@acedatacloud/core
v0.28.3
Published
Shared frontend core for AceDataCloud's browser apps (AuthFrontend / Nexior / PlatformFrontend): http client factory, telemetry, fingerprint, chunk-load recovery, locale + surface helpers, and shared types.
Maintainers
Readme
@acedatacloud/core
Shared frontend core for AceDataCloud's three browser apps:
AuthFrontend—auth.acedata.cloudNexior—studio.acedata.cloud(consumer / chat)PlatformFrontend—platform.acedata.cloud(developer / open API)
These apps had independently re-implemented the same plumbing (axios interceptors, chunk-load recovery, telemetry, fingerprint, locale resolution) and drifted on the details. This package lifts the correctness-sensitive duplication into one place so the apps stop diverging on the parts where divergence causes real auth / observability bugs.
Distribution — published npm package
Consumed via a normal package.json dependency (no submodule, no @common alias):
npm install @acedatacloud/coreThe package is built with plain tsc to ESM (no Vite library mode) so any consumer's
bundler (Vite 8 / 7 / 2) transpiles it cleanly. CI auto-publishes on merge to main,
version-gated: a merge publishes only when package.json's version is not already on npm.
To ship a release, bump the version in package.json in your PR.
Subpath entry points
Import only what you need — peer-dependent modules are subpath-only so they tree-shake:
import { createChunkLoadErrorHandler, makeGetLocale, isWechat } from '@acedatacloud/core/core';
import { createHttpClient } from '@acedatacloud/core/http'; // peer: axios
import { createTelemetry } from '@acedatacloud/core/telemetry'; // peer: aegis-web-sdk (optional)
import { createFingerprintResolver } from '@acedatacloud/core/fingerprint';
import type { IUser, IConnection } from '@acedatacloud/core/types';
import type { ITranslationEnableRequest, ITranslationEnableResponse } from '@acedatacloud/core/types';
import { CURRENCY_LABEL_MAPPING, LOCALE_CURRENCY_MAPPING } from '@acedatacloud/core/constants';
import { debug, getBaseUrlAuth } from '@acedatacloud/core/utils';
import { replaceBrandText } from '@acedatacloud/core/brand-spacing';
import { buildUsageAnalytics, formatUsageAmount, formatUsageElapsed } from '@acedatacloud/core/usage';
import { extractX402PaymentError, resolveX402PaymentError } from '@acedatacloud/core/x402';The root entry (@acedatacloud/core) re-exports the dependency-free surface
(core, types, constants, utils).
Usage analytics
@acedatacloud/core/usage converts API aggregate rows into stacked-bar series,
doughnut slices, and the complete API breakdown used by PlatformFrontend and
Nexior. It preserves duplicate-row summation, date ordering, the 99% spend
threshold, the 20-series cap and the single-leftover exception. Pass the
translated “Others” title; the helper adds the folded API count. Amounts stay
in Credits. The module has no Chart.js, Vue, HTTP, or store dependency.
The consumers retain request filters, locale selection, loading/error state,
stale-response guards and Chart.js options. formatUsageAmount and
formatUsageElapsed preserve their existing table and tooltip formatting.
Shared components
Import the shared stylesheet once in the application entry, then import components from the dedicated subpath:
import '@acedatacloud/core/styles.css';
import { Pagination } from '@acedatacloud/core/components';Pagination preserves the existing Nexior and PlatformFrontend props and change event contract. Its round controls use the shared --adc-radius-round token in styles.css.
The shared crop viewport keeps zoom, rotation, flip, replacement controls, and the crop frame consistent across the three frontends. Applications still own file selection, dialogs, uploads, and brand processing:
import { ImageCropperViewport } from '@acedatacloud/core/components/image-cropper-viewport';
import '@acedatacloud/core/image-cropper.css';
import 'vue-advanced-cropper/dist/style.css';Pass localized labels, src, aspectRatio, and optionally shape, imageRestriction, controlSize, or checkerboard. The exposed getResult() returns null for an empty crop. Set --adc-image-cropper-height on the component for the viewport height.
BackNavigation, StatusBadge, MetaTag, ActionChip, and FilterChip use the same stylesheet:
<BackNavigation :as="RouterLink" :to="parentRoute">All campaigns</BackNavigation>
<StatusBadge tone="info" density="compact">Submitted</StatusBadge>
<MetaTag tone="brand">Vision</MetaTag>
<ActionChip @click="manageKey">Manage key</ActionChip>
<FilterChip :selected="imagesOnly" @click="toggleImages">Images</FilterChip>The application imports RouterLink; the shared component has no router/store dependency. BackNavigation defaults to a non-submitting button, supports as="a" plus href, and forwards routing attributes and events. Parent navigation, history fallbacks, wizard steps, dismissals, OAuth callbacks and dirty guards remain application-owned. An icon-only workspace return needs icon-only and an action-specific aria-label; variant="inverse" supports dark hero surfaces.
Display badges have a soft background, 8px radius, 12px text and 24px height; density="compact" is the 20px table/list variant. Tones are neutral / brand / info / success / warning / danger. Existing primary resolver results map to brand; empty/unknown results map to neutral. State-to-tone mapping remains application-owned. Use MetaTag for information, ActionChip for an action and FilterChip for controlled aria-pressed selection. Static badges do not create live regions. Navigation/chips have 44px touch targets. Semantic foreground/background pairs support light/dark contrast; neutral/brand follow application theme/tenant accent.
The control adapter also normalizes existing Element Plus tags, including closable/interactive tags, to this geometry and soft tone treatment. size="small" is compact; legacy round, effect and large do not create competing shapes or fills. Keep application-specific interactions on the Element tag when a display badge would lose them.
Shared Markdown
import {
MarkdownRenderer,
createMarkdownRenderer,
} from "@acedatacloud/core/markdown";
import "@acedatacloud/core/markdown.css";MarkdownRenderer takes source, reactive options, anchor, linkTarget, and
htmlPolicy props. The default sanitize policy supports native audio/video,
rich text presentation styles, task lists, tables, highlighted code and KaTeX
($, $$, \(, \[, and fenced math). Media always have controls and
preload="none"; scripts, event handlers, frames and unsafe URLs are removed.
Styles follow the application's Element Plus tokens, including dark mode.
Use htmlPolicy="escape" for literal raw HTML. Existing anonymous Studio
surfaces keep the markdownSanitize injection / sanitize prop escape guard.
trusted is available only for existing trusted, generated document content.
Heading anchors are opt-in; in-page links keep their current browsing context.
Citation cards, copy buttons, navigation and application-specific styling stay
with consumers. The pure renderer and Vue adapter also work during SSR.
Consumers use the normal published npm package. Merge and publish the core release before merging adoption PRs in dependent repositories.
Design tokens
styles.css also defines the shared visual baseline without globally styling application elements:
- theme-aware color, text, surface, and border aliases;
- the
4 / 8 / 12 / 16 / 20 / fullradius scale withsmall / control / card / dialogsemantics; - a 4px spacing grid;
- shared font-family, size, weight, line-height, and letter-spacing tokens.
- button levels
xs/sm/md/lg/xlat 20/24/32/40/48px; inline Copy usesxs, prompt expand usessm, and code-block Copy keeps its 30px special case; Element Plus buttons retain their native small/default/large sizes unless an individual component opts in; - focus outline/ring, elevation, motion, and stacking-layer tokens;
- reduced-motion duration overrides.
Consumers opt in by mapping their existing root variables and component styles to --adc-*. Runtime accent colors and light/dark surfaces remain owned by each application's Element Plus theme.
Core control adapter
Import the control adapter after vendor and application styles — including Tailwind — so its variant-safe Element Plus contract wins without !important:
import '@acedatacloud/core/styles.css';
import './assets/scss/style.scss';
import './assets/css/tailwind.css';
import '@acedatacloud/core/controls.css';Keeping controls.css last is load-bearing, not cosmetic. It carries an svg { display: inline-block } reset that undoes Tailwind preflight's svg { display: block } (which otherwise breaks text-align: center on icons). Both selectors have the same specificity, so the reset only wins by coming later — import Tailwind after controls.css and it silently stops working.
The adapter standardizes Button, Input, Select, Textarea, Card, Dialog, and MessageBox while preserving Element's link/text/circle sizes, error states, input-group seams, and card shadow modes. Applications can retain product-specific card styling through --adc-control-card-border, --adc-control-card-shadow, --adc-control-card-shadow-hover, and --adc-control-dialog-shadow; because dialogs and message boxes are teleported, set overlay hooks on :root or body.
Use card-gap on card groups and card-section-gap for the space below a card section. Both use --adc-card-gap, which defaults to the 20px spacing token; an app can set its own value on :root. Element rows keep their own horizontal gutter, and a guttered el-col.grid.card-gap stays a grid.
For groups of cards, add card-gap to their grid, flex container, or el-row.
It keeps a shared 20px gap between cards; on el-row it preserves Element Plus's
horizontal gutter and adds only the vertical gap needed when columns wrap.
Semantic icons
@acedatacloud/core/icons defines one Lucide glyph for each product semantic. Brand logos remain official SVG/brand assets and are not mapped to Lucide. @acedatacloud/core/icons/components statically re-exports the complete reviewed semantic set; the original single-icon subpaths remain available for compatibility.
Consumers that use icon subpaths must exact-pin the tested peer in the same adoption PR:
npm install --save-exact @lucide/[email protected]The manifest is a review source, not a dynamic runtime resolver. Consumer code uses named imports, for example import { AddIcon, SearchIcon } from '@acedatacloud/core/icons/components'; bundlers tree-shake unused named re-exports. Do not use import * as Icons, Lucide DynamicIcon, or arbitrary icon-name strings in production bundles. Legacy call sites are migrated by product meaning, never by globally replacing a Font Awesome glyph.
Layout
src/
├── core/ # Pure TS, zero framework deps
│ ├── chunkLoadError.ts createChunkLoadErrorHandler({ storagePrefix })
│ ├── surface.ts isWechat(), isMobile()
│ └── locale.ts makeGetLocale({ supportedLocales, defaultLocale })
├── http/ # peer: axios
│ └── createHttpClient.ts createHttpClient(config) — request/response interceptor contract
├── telemetry/ # peer: aegis-web-sdk (optional, dynamically imported)
│ └── createTelemetry.ts createTelemetry({ projectId, hostUrl }) — Tencent RUM glue
├── fingerprint/ # FingerprintJS loader injected (no SDK dep — v4/v5 both work)
│ └── createFingerprintResolver.ts
├── types/ # Pure TypeScript interfaces (user, connection, skill, oauthApp)
├── brand-spacing/ # Unicode-aware boundaries; consumers own source/replacement policy
├── constants/ # base URLs plus shared locale/currency mappings
├── utils/ # getBaseUrl* helpers plus the shared console.debug alias
└── index.ts # root re-export of the dependency-free surfaceDesign notes
- Factories, not singletons. Everything that differed across apps (timeout, header
casing, where the token/userId/fingerprint come from, what a 401 does, RUM ext-slot
mapping, FingerprintJS version) is injected via config. The package never imports a store,
a router, or
vuex. - What is intentionally not here: Vuex stores, app-specific routing/auth helpers,
brand identity/policy, and business workflows.
brand-spacingis a dependency-free text-boundary helper; each consumer owns its source variants and replacement brand. Element Plus components are shared only when the existing app implementations and public contracts are the same.
Develop
npm install
npm run typecheck # tsc -p tsconfig.json (no emit)
npm test # vitest (jsdom)
npm run build # tsc -p tsconfig.build.json -> dist/License
Private — AceDataCloud internal.
User acquisition context
@acedatacloud/core/attribution provides createAcquisitionContext for the four
UTM labels and a capture timestamp. It only reads/writes browser cookies; it has
no fetch, visit handle, identity binding, event queue or authentication dependency.
Explicit tagged landings replace the complete tuple. Untagged navigation keeps
it without renewing its seven-day expiry. Signup reads the context once; the
Auth backend freezes it in existing User.metadata. Legacy cookies without a
timestamp remain legacy evidence, rather than receiving an invented first-touch time.
The browser predicate respects DNT/GPC, saved opt-out, public invoice pages and first-party host boundaries. Storage failures do not affect page navigation. The acquisition rules are generated from PlatformBackend/config/marketing_attribution.json. This package contains only the acquisition projection, not service entries or channel inventories. The backend-owned exporter generates both the pinned JSON and TypeScript constant; UTM_KEYS is derived from that constant. Adding an MCP or campaign does not require a core release. Field/window changes require a versioned core release before consumer adoption. See PlatformBackend/scripts/ECOSYSTEM_CONTRACTS.md.
