diff --git a/src/arts/README.md b/src/arts/README.md index 67eab52..ce2bd9a 100644 --- a/src/arts/README.md +++ b/src/arts/README.md @@ -32,7 +32,7 @@ interface ActiveEngine { Conventions: -- Use direct getters (`Auth.current`, `Cache.loading`, `Permissions.lastError`), +- Use direct getters (`Auth.current`, `Cache.loading`, `Perms.lastError`), not a module-specific `.state` object. - Use `loading`, never `pending`, for in-flight work. - Use `onChange()` for snapshot subscriptions. Lower-level clients may expose @@ -111,7 +111,7 @@ errors are typed. | [`lang`](./lang/README.md) | `EngineLang`, `ActiveLang`, `ActiveMonoLang` | i18n: type-safe translations, BCP 47 resolution, plurals, refs, JSON round-trip | — | | [`logr`](./logr/README.md) | `EngineLogger` | Structured logger: levels, transports, filters, vitals, dispose | — | | [`timr`](./timr/README.md) | `EngineTimers`, `ActiveTimers` | Deterministic timer scheduler: clock injection, one-shots, intervals, cancellation, snapshots, backoff | `$libs/timers`, `$logr` (optional) | -| [`fmts`](./fmts/README.md) | `EngineFormats`, `ActiveFormats` | Localized formatting: numbers, currency, units, dates | `$logr` (currency) | +| [`fmts`](./fmts/README.md) | `EngineFormat`, `ActiveFormat` | Localized formatting: numbers, currency, units, dates | `$logr` (currency) | | [`adom`](./adom/README.md) | `ActiveDom` | Reactive DOM service: viewport, breakpoints, attribute writes, scroll lock | `$libs/dom`, `$reactive` | | [`fend`](./fend/README.md) | `ActiveFrontend` | Frontend preferences: theme, mode, dir, density, applied via DOM attrs | `$adom` | | [`sium`](./sium/README.md) | `EngineSium` | Validation contracts: schemas, issues, introspection, Standard Schema interop | `$lang` (optional), `$logr` (optional), `$libs/days`, `$libs/color` | @@ -120,9 +120,9 @@ errors are typed. | [`sess`](./sess/README.md) | `EngineSession`, `ActiveSession` | Session lifecycle: adopt/revoke/refresh, auto-refresh, 401-rescue hook, SvelteKit SSR via `adoptServer` + cookie reader | `$stor`, `$timr`, `$http`, `$logr` (optional) | | [`conn`](./conn/README.md) | `EngineConnections`, `ActiveConnections` | Realtime connection registry: transports, reconnect, heartbeat, request/reply, channels, session bridge | `$timr`, `$logr` (optional), `$sess` bridge (optional) | | [`auth`](./auth/README.md) | `ActiveAuth` (`EngineAuth` in `$svrs/auth`) | Authentication: password flows, CSRF, current session reflector, devices, logout, server-authoritative auth handlers | `$libs/auth`, `$http`, `$cach`, `$svrs/auth` | -| [`perm`](./perm/README.md) | `ActivePermissions` (`EnginePermissions` in `$svrs/perm`) | Authorization: policy runtime adapter, HTTP client/handlers, cache snapshot, `` guard | `$libs/perm`, `$libs/svrs`, `$http`, `$logr` (optional) | +| [`perm`](./perm/README.md) | `ActivePerms` (`EnginePerms` in `$svrs/perm`) | Authorization: policy runtime adapter, HTTP client/handlers, cache snapshot, `` guard | `$libs/perm`, `$libs/svrs`, `$http`, `$logr` (optional) | | [`cach`](./cach/README.md) | `ActiveCache` (`EngineCache` in `$svrs/cache`) | Data cache: deterministic keys, policies, scopes, stale/revalidate, tags, memory/storage adapters | `$libs/cach`, `$stor` (adapter), `$logr` (optional) | -| [`aapp`](./aapp/README.md) | `ActiveApp` | App composition: wires Logger + Lang + Formats + Frontend + Dom + Storage + Http + Timers + Cache; factories for Sess, Conn, Auth, Perm, Sium | every artifact above | +| [`aapp`](./aapp/README.md) | `ActiveApp` | App composition: wires Logger + Lang + Format + Frontend + Dom + Storage + Http + Timers + Cache; factories for Sess, Conn, Auth, Perm, Sium | every artifact above | ## Composition @@ -138,17 +138,17 @@ const App = createActiveApp({ }); App.Lang.t('common.ok'); -App.Formats.currency.format(99.5); -App.setLocale('es-MX'); // propagates to Lang, Formats, Frontend +App.Format.currency.format(99.5); +App.setLocale('es-MX'); // propagates to Lang, Format, Frontend ``` Every member of `App` is **always present**. When the caller does not configure `lang`/`logger`/`formats`, App provides a structurally identical adapter (mono lang, console logger, default-locale formats). Call sites stay -uniform: `App.Lang.t(...)` and `App.Formats.currency.format(...)` work +uniform: `App.Lang.t(...)` and `App.Format.currency.format(...)` work whether or not i18n was configured. -`Sium`, `Session`, `Connections`, `Auth` and `Permissions` are exposed as +`Sium`, `Session`, `Connections`, `Auth` and `Perms` are exposed as **factories** because they are feature/page-scoped: App injects shared services, but construction is explicit at the call site. @@ -156,7 +156,7 @@ services, but construction is explicit at the call site. const sium = createEngineSium({ lang: App.Lang, logger: App.Logger }); const Connections = App.createActiveConnections(); const Auth = App.createActiveAuth({ initial: data.auth }); -const Permissions = App.createActivePermissions({ endpoint: '/permissions' }); +const Perms = App.createActivePerms({ endpoint: '/permissions' }); ``` See `aapp/README.md` for the full composition contract. diff --git a/src/arts/active-app/README.md b/src/arts/active-app/README.md index 0c13684..95a4b8d 100644 --- a/src/arts/active-app/README.md +++ b/src/arts/active-app/README.md @@ -20,7 +20,7 @@ const App = createActiveApp({ App.setLocale('es-MX'); App.Lang.t('common.ok'); -App.Formats.currency.format(12.5); +App.Format.currency.format(12.5); App.Frontend.setTheme('forest'); App.Logger.info('boot', 'app ready'); @@ -33,7 +33,7 @@ App.dispose(); | -------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `App.Logger` | yes | engine default — `level: WARN` + `consoleTransport()`. Pass `{ level: LogLevel.NONE, transports: [] }` for silence | | `App.Lang` | yes | mono — `t('a.b')` returns `'a.b'`, `t('a.b\|Fallback')` returns `'Fallback'`, and DEV warns once per unresolved path through Logger under `lang.mono` | -| `App.Formats` | yes | real, locale = `DEFAULT_LOCALE` (`'en-US'`) | +| `App.Format` | yes | real, locale = `DEFAULT_LOCALE` (`'en-US'`) | | `App.Frontend` | yes | real with default theme/mode/density | | `App.Dom` | yes | real with default breakpoints | | `App.Storage` | yes | in-memory adapter (resets on reload). Configure `storage: { adapter: localAdapter }` for real persistence; storage diagnostics are wired through the shared Logger | @@ -43,12 +43,12 @@ App.dispose(); | `App.Cache` | yes | `ActiveCache` backed by memory by default. Configure `cache: { adapter, policies, scopeResolver, autoInvalidateOn }` for persistence, policies or app-event reactions | | `App.Sess` | no | created lazily through `App.createActiveSession(...)`. Logger is injected automatically; storage, refresh/revoke handlers and HTTP hooks remain explicit so auth policy does not become hidden magic | | `App.Auth` | no | created lazily through `App.createActiveAuth(...)`. App injects `Http`, `Cache` and `Logger`; the server authority remains `$svrs/auth` | -| `App.Permissions` | no | created lazily through `App.createActivePermissions(...)`. App injects `Http`, `Logger` and `Bus`; automatic invalidation uses `autoInvalidateOn` | +| `App.Perms` | no | created lazily through `App.createActivePerms(...)`. App injects `Http`, `Logger` and `Bus`; automatic invalidation uses `autoInvalidateOn` | | `Connections` | no | created lazily through `App.createActiveConnections(...)`. App injects Logger, Timers and Bus; automatic reauth uses `autoReauthOn` plus each connection's own `session` option | Server-authoritative engines that have a browser reflector live under `$svrs/*`: use `$svrs/auth` for `createEngineAuth()` and auth HTTP handlers, -`$svrs/perm` for `createEnginePermissions()` and authorization handlers, and +`$svrs/perm` for `createEnginePerms()` and authorization handlers, and `$svrs/cache` for `createEngineCache()` in services, repositories or server load code. `aapp` composes only the active/client side. @@ -175,7 +175,7 @@ not from the event payload. ## What it solves - **Single locale source.** `App.setLocale('es-MX')` propagates to `Lang`, - `Formats` and `Frontend` through a shared `LocaleSource`. No bridge code per + `Format` and `Frontend` through a shared `LocaleSource`. No bridge code per call site. - **Single logger.** Built once and piped into `Lang.setLogger` so every artifact emits structured entries through the same transports (console, @@ -183,7 +183,7 @@ not from the event payload. - **Single event bus.** `App.Bus` carries app-level facts. App translates module events to `app.*`; cache, permissions and connections only mutate themselves when their own `auto*On` options opt in. -- **Uniform call sites.** `App.Lang.t(label)` and `App.Formats.*` always work, +- **Uniform call sites.** `App.Lang.t(label)` and `App.Format.*` always work, whether or not the caller configured i18n or fmts. No null checks. - **Single lifecycle.** `App.dispose()` tears down the optional session, bus, timers, persistence bridge, frontend, dom, formats, storage, lang and @@ -198,7 +198,7 @@ not from the event payload. Logger. 3. **Storage** — built next so Frontend can read persisted preferences before construction. Storage diagnostics are wired to the shared Logger. -4. **Formats** — built with a `localeSource` derived from Lang. +4. **Format** — built with a `localeSource` derived from Lang. 5. **Dom** — built before Frontend. 6. **Frontend** — receives Dom and the same `localeSource`. When `frontend.persist` is configured, persisted values seed the initial @@ -221,7 +221,7 @@ not from the event payload. 12. **Connections** — created lazily via `App.createActiveConnections(...)`. App injects Logger, Timers and Bus; `autoReauthOn` controls app-event reactions. -13. **Permissions** — created lazily via `App.createActivePermissions(...)`. +13. **Perms** — created lazily via `App.createActivePerms(...)`. App injects Logger, Http and Bus; endpoint/defaults can be provided either in `createActiveApp({ permissions })` or at the factory call site. 14. **Auth** — created lazily via `App.createActiveAuth(...)`. App injects @@ -249,7 +249,7 @@ const App = createActiveApp({ frontend: { theme: 'base' } }); -App.Formats.currency.format(99.5); // "99,50 €" with default locale +App.Format.currency.format(99.5); // "99,50 €" with default locale ``` `App.Lang` is mono — components calling `App.Lang.t('actions.save|Save')` @@ -268,7 +268,7 @@ tracking, attribute writes) no-op in SSR. ## Locale flow -Lang is the single source of truth. Formats and Frontend subscribe to it via a +Lang is the single source of truth. Format and Frontend subscribe to it via a common `LocaleSource` (`$locale`). The locale value is BCP 47: ```ts @@ -281,7 +281,7 @@ See `$lang/README.md` for the BCP 47 resolution rules in `Lang.t()` / `Lang.ts()`. When `lang` is not configured, `App.setLocale` still updates the mono lang's -internal locale and notifies Formats/Frontend — locale switching keeps working. +internal locale and notifies Format/Frontend — locale switching keeps working. ### SSR locale resolution + hydration @@ -511,7 +511,7 @@ export const cart = { ``` Pages and components import `cart` directly. The "infrastructure" artifacts -(Logger, Lang, Formats, Frontend, Dom) live in App because they are +(Logger, Lang, Format, Frontend, Dom) live in App because they are cross-cutting and need uniform configuration. Domain state (current user, cart, session, feature flags) is application-specific — putting it under `App.Stores` would couple the framework to a bucket of unrelated nouns. @@ -551,7 +551,7 @@ appended — both sinks receive every entry. interface ActiveAppOptions { logger?: LoggerOptions; lang?: { schema: S; defaultLocale?: SupportedLocale; fallbackChain?: SupportedLocale[] }; - formats?: Omit; + formats?: Omit; frontend?: Omit & { persist?: FrontendPersist; }; @@ -562,7 +562,7 @@ interface ActiveAppOptions { bus?: Omit; cache?: Omit; connections?: Omit; - permissions?: Omit; + permissions?: Omit; auth?: Omit; orchestration?: 'standard' | 'silent' | false | readonly ActiveAppOrchestrationTranslator[]; } @@ -570,7 +570,7 @@ interface ActiveAppOptions { interface ActiveApp { readonly Logger: EngineLogger; readonly Lang: ActiveLang; - readonly Formats: ActiveFormats; + readonly Format: ActiveFormat; readonly Frontend: ActiveFrontend; readonly Dom: ActiveDom; readonly Storage: ActiveStorage; @@ -590,13 +590,13 @@ interface ActiveApp { createActiveConnections( options?: Omit ): ActiveConnections; - createActivePermissions( - options?: Partial> - ): ActivePermissions; + createActivePerms( + options?: Partial> + ): ActivePerms; createActiveAuth(options?: Omit): ActiveAuth; readonly Sess: ActiveSession | undefined; - readonly Permissions: ActivePermissions | undefined; + readonly Perms: ActivePerms | undefined; readonly Auth: ActiveAuth | undefined; dispose(): void; @@ -608,7 +608,7 @@ interface ActiveApp { Sium is a validation library that reaches into per-page data — login forms, profile editors, signup wizards. Putting it in App would force every page (including those without forms) to load the entire schema/types/issues machinery -just to use Lang or Formats. Keeping Sium page-scoped means: +just to use Lang or Format. Keeping Sium page-scoped means: - Pages without validation pay nothing for it. - Each form can use a sium engine tuned to its own needs (custom logger diff --git a/src/arts/active-app/active-app.svelte.ts b/src/arts/active-app/active-app.svelte.ts index 4a6523a..a5a566a 100644 --- a/src/arts/active-app/active-app.svelte.ts +++ b/src/arts/active-app/active-app.svelte.ts @@ -7,15 +7,15 @@ import { createActiveCache } from '$cache/active-cache.svelte'; import { createActiveConnections as createActiveConnectionsRegistry } from '$connection/active-connections.svelte'; import type { ActiveConnections, ActiveConnectionsOptions, ConnectionMap } from '$connection/types'; import { createActiveFrontend } from '$frontend/active-frontend.svelte'; -import { createActiveFormats } from '$formats/active-formats.svelte'; +import { createActiveFormat } from '$format/active-formats.svelte'; import { createEngineHttp } from '$http/engine-http'; import type { ActiveLang, LangNode } from '$libs/lang'; import { createActiveLang } from '$lang/active-lang.svelte'; import { createActiveMonoLang } from '$lang/mono-lang.svelte'; import { createEngineLogger } from '$logger/engine-logger'; -import { createActivePermissions as createActivePermissionsClient } from '$permissions/active-permissions.svelte'; -import { PermInvalidEndpointError } from '$permissions/errors'; -import type { ActivePermissions, ActivePermissionsOptions } from '$permissions/types'; +import { createActivePerms as createActivePermsClient } from '$perm/active-permissions.svelte'; +import { PermInvalidEndpointError } from '$perm/errors'; +import type { ActivePerms, ActivePermsOptions } from '$perm/types'; import { createActiveSession } from '$session/active-session.svelte'; import { SessionAlreadyCreatedError } from '$session/errors'; import type { ActiveSession, EngineSessionOptions } from '$session/types'; @@ -35,13 +35,13 @@ import { APP_ERROR_ALREADY_CREATED_PERMISSIONS, APP_ERROR_ALREADY_CREATED_AUTH, APP_ERROR_ALREADY_CREATED_SESSION, - APP_ERROR_CREATE_PERMISSION_ENDPOINT_REQUIRED, + APP_ERROR_CREATE_PERM_ENDPOINT_REQUIRED, APP_ORCHESTRATION_SILENT, APP_ORCHESTRATION_STANDARD, APP_ORCHESTRATION_TRANSLATOR_CONNECTIVITY, APP_ORCHESTRATION_TRANSLATOR_DISPOSE, APP_ORCHESTRATION_TRANSLATOR_IDENTITY, - APP_ORCHESTRATION_TRANSLATOR_PERMISSION_REFRESH, + APP_ORCHESTRATION_TRANSLATOR_PERM_REFRESH, APP_ORCHESTRATION_TRANSLATOR_TENANT_SWITCHED, APP_MODULE } from './consts.ts'; @@ -86,7 +86,7 @@ export function createActiveApp( onLocaleChange: (fn: (locale: string) => void) => Lang.onLocaleChange(fn) }; - const Formats = createActiveFormats({ + const Format = createActiveFormat({ ...options.formats, localeSource }); @@ -133,7 +133,7 @@ export function createActiveApp( let disposed = false; let Sess: ActiveSession | undefined; - let Permissions: ActivePermissions | undefined; + let Perms: ActivePerms | undefined; let Auth: ActiveAuth | undefined; // eslint-disable-next-line svelte/prefer-svelte-reactivity -- disposal registry is not rendered state. const connectionRegistries = new Set(); @@ -141,7 +141,7 @@ export function createActiveApp( const app: ActiveApp = { Logger, Lang, - Formats, + Format, Frontend, Dom, Storage, @@ -154,8 +154,8 @@ export function createActiveApp( return Sess; }, - get Permissions() { - return Permissions; + get Perms() { + return Perms; }, get Auth() { @@ -206,10 +206,10 @@ export function createActiveApp( return built; }, - createActivePermissions( - permissionOptions: Partial> = {} - ): ActivePermissions { - if (Permissions !== undefined) { + createActivePerms( + permissionOptions: Partial> = {} + ): ActivePerms { + if (Perms !== undefined) { throw new AappAlreadyCreatedError(APP_ERROR_ALREADY_CREATED_PERMISSIONS); } const merged = { @@ -217,16 +217,16 @@ export function createActiveApp( ...permissionOptions }; if (!merged.endpoint) { - throw new PermInvalidEndpointError(APP_ERROR_CREATE_PERMISSION_ENDPOINT_REQUIRED); + throw new PermInvalidEndpointError(APP_ERROR_CREATE_PERM_ENDPOINT_REQUIRED); } - const built = createActivePermissionsClient({ + const built = createActivePermsClient({ ...merged, endpoint: merged.endpoint, logger: Logger, http: Http, bus: Bus }); - Permissions = built; + Perms = built; return built; }, @@ -243,7 +243,7 @@ export function createActiveApp( http: createEngineHttpAuthClient(Http), cache: createAuthCacheInvalidator({ cache: Cache, - permissions: () => Permissions + permissions: () => Perms }) }); Auth = built; @@ -258,8 +258,8 @@ export function createActiveApp( } Auth?.dispose(); Auth = undefined; - Permissions?.dispose(); - Permissions = undefined; + Perms?.dispose(); + Perms = undefined; for (const registry of connectionRegistries) registry.dispose(); connectionRegistries.clear(); detachSessionTranslator?.(); @@ -271,7 +271,7 @@ export function createActiveApp( teardownPersistence(); Frontend.dispose(); Dom.dispose(); - Formats.dispose(); + Format.dispose(); Storage.dispose(); Lang.dispose(); Logger.dispose(); @@ -295,7 +295,7 @@ function resolveActiveAppOrchestration( const STANDARD_ORCHESTRATION_TRANSLATORS = [ APP_ORCHESTRATION_TRANSLATOR_IDENTITY, - APP_ORCHESTRATION_TRANSLATOR_PERMISSION_REFRESH, + APP_ORCHESTRATION_TRANSLATOR_PERM_REFRESH, APP_ORCHESTRATION_TRANSLATOR_TENANT_SWITCHED, APP_ORCHESTRATION_TRANSLATOR_CONNECTIVITY, APP_ORCHESTRATION_TRANSLATOR_DISPOSE diff --git a/src/arts/active-app/consts.ts b/src/arts/active-app/consts.ts index 24eb076..d1e0d8d 100644 --- a/src/arts/active-app/consts.ts +++ b/src/arts/active-app/consts.ts @@ -9,7 +9,7 @@ export const APP_CONNECTION_CLOSE_REASON_IDENTITY_CLEARED = 'identity_cleared'; export const APP_ORCHESTRATION_STANDARD = 'standard'; export const APP_ORCHESTRATION_SILENT = 'silent'; export const APP_ORCHESTRATION_TRANSLATOR_IDENTITY = 'identity'; -export const APP_ORCHESTRATION_TRANSLATOR_PERMISSION_REFRESH = 'permissions-refresh'; +export const APP_ORCHESTRATION_TRANSLATOR_PERM_REFRESH = 'permissions-refresh'; export const APP_ORCHESTRATION_TRANSLATOR_TENANT_SWITCHED = 'tenant-switched'; export const APP_ORCHESTRATION_TRANSLATOR_CONNECTIVITY = 'connectivity'; export const APP_ORCHESTRATION_TRANSLATOR_DISPOSE = 'dispose'; @@ -20,10 +20,10 @@ export const APP_ERROR_ALREADY_CREATED_SESSION = `[${APP_MODULE}] App.createActiveSession() called twice — only one session per App.`; export const APP_ERROR_ALREADY_CREATED_PERMISSIONS = - `[${APP_MODULE}] App.createActivePermissions() called twice — only one permissions client per App.`; + `[${APP_MODULE}] App.createActivePerms() called twice — only one permissions client per App.`; export const APP_ERROR_ALREADY_CREATED_AUTH = `[${APP_MODULE}] App.createActiveAuth() called twice — only one active auth client per App.`; -export const APP_ERROR_CREATE_PERMISSION_ENDPOINT_REQUIRED = - `[${APP_MODULE}] App.createActivePermissions() requires endpoint via options.permissions or argument.`; +export const APP_ERROR_CREATE_PERM_ENDPOINT_REQUIRED = + `[${APP_MODULE}] App.createActivePerms() requires endpoint via options.permissions or argument.`; diff --git a/src/arts/active-app/index.ts b/src/arts/active-app/index.ts index d192cf0..bb0e4f0 100644 --- a/src/arts/active-app/index.ts +++ b/src/arts/active-app/index.ts @@ -5,7 +5,7 @@ export { APP_ORCHESTRATION_TRANSLATOR_CONNECTIVITY, APP_ORCHESTRATION_TRANSLATOR_DISPOSE, APP_ORCHESTRATION_TRANSLATOR_IDENTITY, - APP_ORCHESTRATION_TRANSLATOR_PERMISSION_REFRESH, + APP_ORCHESTRATION_TRANSLATOR_PERM_REFRESH, APP_ORCHESTRATION_TRANSLATOR_TENANT_SWITCHED, APP_MODULE } from './consts'; diff --git a/src/arts/active-app/integrations/auth-cache.ts b/src/arts/active-app/integrations/auth-cache.ts index 93f0a31..ec12065 100644 --- a/src/arts/active-app/integrations/auth-cache.ts +++ b/src/arts/active-app/integrations/auth-cache.ts @@ -1,11 +1,11 @@ import { CACHE_SCOPE_PUBLIC } from '$libs/cache'; import type { AuthClientCachePort } from '$libs/auth/contracts'; import type { ActiveCache } from '$cache'; -import type { ActivePermissions } from '$permissions'; +import type { ActivePerms } from '$perm'; export function createAuthCacheInvalidator(input: { readonly cache: ActiveCache; - readonly permissions: () => ActivePermissions | undefined; + readonly permissions: () => ActivePerms | undefined; }): AuthClientCachePort { return { async invalidate(event) { diff --git a/src/arts/active-app/test/active-app.test.ts b/src/arts/active-app/test/active-app.test.ts index 8e2db0d..eb190cb 100644 --- a/src/arts/active-app/test/active-app.test.ts +++ b/src/arts/active-app/test/active-app.test.ts @@ -2,7 +2,7 @@ * aapp — composition smoke tests. * * Verifies that createActiveApp() wires every artifact correctly: - * - shared locale flows from Lang to Formats and Frontend + * - shared locale flows from Lang to Format and Frontend * - logger reaches lang's setLogger (via the adapter) * - mono lang activates when no schema is provided and warns via the * shared logger (once per unresolved path) @@ -39,8 +39,8 @@ import { AUTH_ROUTE_PATHS, AUTH_SESSION_STATUSES } from '$libs/auth'; -import { PERMISSION_EFFECT_ALLOW } from '$libs/permissions'; -import { PERMISSION_AUTO_INVALIDATE_STANDARD } from '$permissions'; +import { PERM_EFFECT_ALLOW } from '$libs/perm'; +import { PERM_AUTO_INVALIDATE_STANDARD } from '$perm'; import { SESSION_EVENT_LIFECYCLE_REVOKED } from '$session/consts'; const schema = { @@ -70,18 +70,18 @@ describe('createActiveApp — composition', () => { 'Bus', 'Cache', 'Dom', - 'Formats', + 'Format', 'Frontend', 'Http', 'Lang', 'Logger', - 'Permissions', + 'Perms', 'Sess', 'Storage', 'Timers', 'createActiveAuth', 'createActiveConnections', - 'createActivePermissions', + 'createActivePerms', 'createActiveSession', 'createSiumEngine', 'dispose', @@ -92,7 +92,7 @@ describe('createActiveApp — composition', () => { expect(App.Logger).toBeDefined(); expect(App.Lang).toBeDefined(); - expect(App.Formats).toBeDefined(); + expect(App.Format).toBeDefined(); expect(App.Frontend).toBeDefined(); expect(App.Dom).toBeDefined(); expect(App.Storage).toBeDefined(); @@ -101,7 +101,7 @@ describe('createActiveApp — composition', () => { expect(App.Bus).toBeDefined(); expect(App.Cache).toBeDefined(); expect(App.Sess).toBeUndefined(); - expect(App.Permissions).toBeUndefined(); + expect(App.Perms).toBeUndefined(); expect(App.Auth).toBeUndefined(); App.dispose(); @@ -151,7 +151,7 @@ describe('createActiveApp — composition', () => { 'ts' ]); - expect(surface(App.Formats)).toEqual([ + expect(surface(App.Format)).toEqual([ 'currency', 'dates', 'dispose', @@ -160,7 +160,7 @@ describe('createActiveApp — composition', () => { 'setLocale', 'units' ]); - expect(surface(App.Formats.numbers)).toEqual([ + expect(surface(App.Format.numbers)).toEqual([ 'clearDecimalSeparator', 'clearGroupSeparator', 'clearGrouping', @@ -185,7 +185,7 @@ describe('createActiveApp — composition', () => { 'setGrouping', 'setLocale' ]); - expect(surface(App.Formats.currency)).toEqual([ + expect(surface(App.Format.currency)).toEqual([ 'clearCurrency', 'convert', 'convertAs', @@ -201,7 +201,7 @@ describe('createActiveApp — composition', () => { 'setCurrency', 'setLocale' ]); - expect(surface(App.Formats.units)).toEqual([ + expect(surface(App.Format.units)).toEqual([ 'clearSystem', 'convert', 'convertToDefault', @@ -218,7 +218,7 @@ describe('createActiveApp — composition', () => { 'setLocale', 'setSystem' ]); - expect(surface(App.Formats.dates)).toEqual([ + expect(surface(App.Format.dates)).toEqual([ 'clearDateOrder', 'clearHourCycle', 'dispose', @@ -353,7 +353,7 @@ describe('createActiveApp — composition', () => { }); const Session = App.createActiveSession(); const Connections = App.createActiveConnections(); - const Permissions = App.createActivePermissions({ + const Perms = App.createActivePerms({ endpoint: 'https://active.test/permissions' }); const Auth = App.createActiveAuth({ @@ -407,7 +407,7 @@ describe('createActiveApp — composition', () => { 'size', 'states' ]); - expect(surface(Permissions)).toEqual([ + expect(surface(Perms)).toEqual([ 'batch', 'can', 'check', @@ -512,7 +512,7 @@ describe('createActiveApp — composition', () => { App.dispose(); }); - it('exposes Logger, Lang, Formats, Frontend, Dom (no Sium)', () => { + it('exposes Logger, Lang, Format, Frontend, Dom (no Sium)', () => { const App = createActiveApp({ lang: { schema, defaultLocale: 'es' }, logger: { level: LogLevel.NONE, transports: [] } @@ -520,7 +520,7 @@ describe('createActiveApp — composition', () => { expect(App.Logger).toBeDefined(); expect(App.Lang).toBeDefined(); - expect(App.Formats).toBeDefined(); + expect(App.Format).toBeDefined(); expect(App.Frontend).toBeDefined(); expect(App.Dom).toBeDefined(); expect((App as unknown as { Sium?: unknown }).Sium).toBeUndefined(); @@ -624,13 +624,13 @@ describe('createActiveApp — composition', () => { const App = createActiveApp({ logger: { level: LogLevel.NONE, transports: [] } }); - const Permissions = App.createActivePermissions({ + const Perms = App.createActivePerms({ endpoint: 'https://active.test/permissions', - autoInvalidateOn: PERMISSION_AUTO_INVALIDATE_STANDARD + autoInvalidateOn: PERM_AUTO_INVALIDATE_STANDARD }); - Permissions.hydrate({ + Perms.hydrate({ decisions: { - 'post.read:p1': { effect: PERMISSION_EFFECT_ALLOW, policy: 'test.allow' } + 'post.read:p1': { effect: PERM_EFFECT_ALLOW, policy: 'test.allow' } } }); let sizeSeenDuringDispose = -1; @@ -641,7 +641,7 @@ describe('createActiveApp — composition', () => { identity: { from: 'identified', to: 'none' }, cause: APP_USER_IDENTITY_CAUSE_SESSION_REVOKED }); - sizeSeenDuringDispose = Permissions.size; + sizeSeenDuringDispose = Perms.size; }); App.dispose(); @@ -710,9 +710,9 @@ describe('createActiveApp — composition', () => { retry: { limit: 0 } } }); - const Permissions = App.createActivePermissions({ + const Perms = App.createActivePerms({ endpoint: 'https://active.test/permissions', - autoInvalidateOn: PERMISSION_AUTO_INVALIDATE_STANDARD + autoInvalidateOn: PERM_AUTO_INVALIDATE_STANDARD }); App.Bus.on(APP_EVENT_DISPOSE_STARTING, () => { void Promise.resolve().then(() => { @@ -729,7 +729,7 @@ describe('createActiveApp — composition', () => { }); }); - const pendingPermission = Permissions.check({ + const pendingPerm = Perms.check({ action: 'post.read', resource: { type: 'post', id: 'p1' }, context: {} @@ -743,19 +743,19 @@ describe('createActiveApp — composition', () => { await Promise.resolve(); App.dispose(); - permissionReply.resolve({ effect: PERMISSION_EFFECT_ALLOW, policy: 'test.allow' }); + permissionReply.resolve({ effect: PERM_EFFECT_ALLOW, policy: 'test.allow' }); cacheReply.resolve({ actorId: 'actor-ada' }); - await expect(pendingPermission).resolves.toMatchObject({ - effect: PERMISSION_EFFECT_ALLOW + await expect(pendingPerm).resolves.toMatchObject({ + effect: PERM_EFFECT_ALLOW }); await expect(pendingCache).resolves.toEqual({ actorId: 'actor-ada' }); await Promise.resolve(); - expect(Permissions.snapshot().decisions).toEqual({}); + expect(Perms.snapshot().decisions).toEqual({}); expect(cacheAdapter.inspect().entries).toHaveLength(0); expect(deferredPublishError).toBeDefined(); - expect(Permissions.size).toBe(0); + expect(Perms.size).toBe(0); }); it('creates App-wired connection registries', async () => { @@ -850,7 +850,7 @@ describe('createActiveApp — composition', () => { }); } if (path === '/permissions/check') { - return json({ effect: PERMISSION_EFFECT_ALLOW, policy: 'remote.allow' }); + return json({ effect: PERM_EFFECT_ALLOW, policy: 'remote.allow' }); } return new Response(null, { status: 404 }); }) as typeof fetch; @@ -863,17 +863,17 @@ describe('createActiveApp — composition', () => { } } }); - const Permissions = App.createActivePermissions({ + const Perms = App.createActivePerms({ endpoint: 'https://active.test/permissions' }); const Auth = App.createActiveAuth(); - await Permissions.check({ action: 'post.read', resource: { type: 'post', id: 'p1' } }); - expect(Permissions.size).toBe(1); + await Perms.check({ action: 'post.read', resource: { type: 'post', id: 'p1' } }); + expect(Perms.size).toBe(1); await Auth.signInPassword({ identifier: 'ada@example.com', password: 'correct horse' }); - expect(Permissions.size).toBe(0); + expect(Perms.size).toBe(0); expect(invalidatedTags).toEqual([ AUTH_CACHE_TAGS.AUTH_CURRENT, AUTH_CACHE_TAGS.AUTH_DEVICES, @@ -947,7 +947,7 @@ describe('createActiveApp — opt-in defaults', () => { const App = createActiveApp(); expect(App.Logger).toBeDefined(); expect(App.Lang).toBeDefined(); - expect(App.Formats).toBeDefined(); + expect(App.Format).toBeDefined(); expect(App.Frontend).toBeDefined(); expect(App.Dom).toBeDefined(); App.dispose(); @@ -1043,28 +1043,28 @@ describe('createActiveApp — opt-in defaults', () => { App.dispose(); }); - it('Formats receives the mono locale via localeSource', () => { + it('Format receives the mono locale via localeSource', () => { const App = createActiveApp({ logger: { level: LogLevel.NONE, transports: [] } }); - // Default mono locale is 'en'; Formats picks it up. - expect(App.Formats.getLocale()).toBe('en'); + // Default mono locale is 'en'; Format picks it up. + expect(App.Format.getLocale()).toBe('en'); App.setLocale('es-ES'); expect(App.Lang.getLocale()).toBe('es-ES'); - expect(App.Formats.getLocale()).toBe('es-ES'); + expect(App.Format.getLocale()).toBe('es-ES'); App.dispose(); }); - it('Formats accepts its own options (e.g. fixed currency) without lang', () => { + it('Format accepts its own options (e.g. fixed currency) without lang', () => { const App = createActiveApp({ logger: { level: LogLevel.NONE, transports: [] }, formats: { currency: { currency: 'EUR' } } }); - expect(App.Formats.currency.getCurrency()).toBe('EUR'); + expect(App.Format.currency.getCurrency()).toBe('EUR'); App.dispose(); }); }); diff --git a/src/arts/active-app/test/ecosystem.integration.test.ts b/src/arts/active-app/test/ecosystem.integration.test.ts index f1028e4..f706309 100644 --- a/src/arts/active-app/test/ecosystem.integration.test.ts +++ b/src/arts/active-app/test/ecosystem.integration.test.ts @@ -38,9 +38,9 @@ import { } from '$libs/auth'; import { LogLevel, type LogEntry } from '$logger'; import { - PERMISSION_EFFECT_ALLOW, - PERMISSION_EFFECT_NOT_APPLICABLE, - PERMISSION_AUTO_INVALIDATE_STANDARD, + PERM_EFFECT_ALLOW, + PERM_EFFECT_NOT_APPLICABLE, + PERM_AUTO_INVALIDATE_STANDARD, actor, allow, and, @@ -50,22 +50,22 @@ import { rel, type ResourceRef, type SubjectRef -} from '$permissions'; -import { createEnginePermissions, createPermissionHttpHandlers } from '$svrs/permissions'; +} from '$perm'; +import { createEnginePerms, createPermHttpHandlers } from '$svrs/perm'; import { createMemoryAdapter } from '$storage'; import { SESSION_EVENT_LIFECYCLE_ADOPTED } from '$session/consts'; import { APP_EVENT_USER_IDENTITY_CHANGED, APP_USER_IDENTITY_CAUSE_SESSION_ADOPTED, APP_USER_IDENTITY_CAUSE_SESSION_REVOKED, - onAppPermissionsRefreshRequested, - publishAppPermissionsRefreshRequested, + onAppPermsRefreshRequested, + publishAppPermsRefreshRequested, publishAppTenantSwitched, publishAppUserIdentityChanged } from '$libs/active-app/events'; import { SESSION_EVENT_LIFECYCLE_REVOKED } from '$session/consts'; -const PERMISSION_ENDPOINT = 'https://ecosystem.test/api/permissions'; +const PERM_ENDPOINT = 'https://ecosystem.test/api/permissions'; const HTTP_PROJECT_PATH = '/api/demo/project'; const TENANT_ID = 'tenant-acme'; const SECOND_TENANT_ID = 'tenant-umbrella'; @@ -84,8 +84,8 @@ const CHAT_CONNECTION_NAME = 'chat'; const LOG_CATEGORY = 'test.ecosystem'; const LOG_MESSAGE_BOOT = 'ecosystem.boot'; const ACK_FRAME_TYPE = 'test.ack'; -const BACKEND_PERMISSION_CHANGED_FRAME_TYPE = 'permissions.changed'; -const BACKEND_PERMISSION_CHANGED_CAUSE = 'websocket:permissions.changed'; +const BACKEND_PERM_CHANGED_FRAME_TYPE = 'perm.changed'; +const BACKEND_PERM_CHANGED_CAUSE = 'websocket:permissions.changed'; const BACKEND_SESSION_REVOKED_FRAME_TYPE = 'session.revoked'; const TOKEN_ADA = 'token-ada'; const TOKEN_LINUS = 'token-linus'; @@ -143,8 +143,8 @@ describe('ActiveApp — total ecosystem integration', () => { tenantId: TENANT_ID, locked: false }; - const permissionEngine = createPermissionEngine(); - const permissionHandlers = createPermissionHttpHandlers(permissionEngine, actorRef); + const permissionEngine = createPermEngine(); + const permissionHandlers = createPermHttpHandlers(permissionEngine, actorRef); let projectFetches = 0; const cacheEvents: CacheEvent[] = []; @@ -239,7 +239,7 @@ describe('ActiveApp — total ecosystem integration', () => { expect(App.Lang.t('demo.title')).toBe('Demo'); App.setLocale('ar'); expect(App.Frontend.getDir()).toBe('rtl'); - expect(App.Formats.currency.format(1200).length).toBeGreaterThan(0); + expect(App.Format.currency.format(1200).length).toBeGreaterThan(0); expect(App.Dom.resolve({ base: 'mobile', md: 'desktop' })).toBeDefined(); const storageEntry = App.Storage.entry('draft', () => ({ title: 'Atlas' })); @@ -268,29 +268,29 @@ describe('ActiveApp — total ecosystem integration', () => { }); expect(Sess.current?.user?.id).toBe(ACTOR_ID); - const Permissions = App.createActivePermissions({ - endpoint: PERMISSION_ENDPOINT, + const Perms = App.createActivePerms({ + endpoint: PERM_ENDPOINT, scopeKey: () => `${ACTOR_ID}:${actorRole}` }); - const decision = await Permissions.check({ + const decision = await Perms.check({ action: PROJECT_ACTION_UPDATE, resource, context: { risk: { mfa: true } } }); - expect(decision.effect).toBe(PERMISSION_EFFECT_ALLOW); + expect(decision.effect).toBe(PERM_EFFECT_ALLOW); actorRole = ROLE_VIEWER; - Permissions.invalidate(); - const viewerDecision = await Permissions.check({ + Perms.invalidate(); + const viewerDecision = await Perms.check({ action: PROJECT_ACTION_UPDATE, resource, context: { risk: { mfa: true } } }); - expect(viewerDecision.effect).toBe(PERMISSION_EFFECT_NOT_APPLICABLE); - expect(Permissions.size).toBeGreaterThan(0); + expect(viewerDecision.effect).toBe(PERM_EFFECT_NOT_APPLICABLE); + expect(Perms.size).toBeGreaterThan(0); actorRole = ROLE_ADMIN; - Permissions.invalidate(); + Perms.invalidate(); const project = await App.Cache.query({ key: ['project', PROJECT_ID], @@ -359,7 +359,7 @@ describe('ActiveApp — total ecosystem integration', () => { await Auth.signOut(); expect(Auth.authenticated).toBe(false); - expect(Permissions.size).toBe(0); + expect(Perms.size).toBe(0); expect(cacheEvents.some((event) => event.type === CACHE_EVENT_INVALIDATE)).toBe(true); } finally { offCacheEvents(); @@ -405,14 +405,14 @@ describe('ActiveApp — total ecosystem integration', () => { it('keeps translators and consumer reactions separated', async () => { let activeActorId = ACTOR_ID; - const permissionEngine = createPermissionEngine(); + const permissionEngine = createPermEngine(); const actorRef = (): SubjectRef => ({ type: 'user', id: activeActorId, role: ROLE_ADMIN, tenantIds: [TENANT_ID] }); - const permissionHandlers = createPermissionHttpHandlers(permissionEngine, actorRef); + const permissionHandlers = createPermHttpHandlers(permissionEngine, actorRef); const appFetch = (async (input: RequestInfo | URL, init?: RequestInit): Promise => { const path = requestPath(input); if (path.startsWith('/api/permissions')) { @@ -458,10 +458,10 @@ describe('ActiveApp — total ecosystem integration', () => { const Sess = App.createActiveSession({ storage: { adapter: createMemoryAdapter(), key: 'session' } }); - const Permissions = App.createActivePermissions({ - endpoint: PERMISSION_ENDPOINT, + const Perms = App.createActivePerms({ + endpoint: PERM_ENDPOINT, scopeKey: () => activeActorId, - autoInvalidateOn: PERMISSION_AUTO_INVALIDATE_STANDARD + autoInvalidateOn: PERM_AUTO_INVALIDATE_STANDARD }); const Connections = App.createActiveConnections({ autoReauthOn: CONNECTION_AUTO_REAUTH_STANDARD @@ -491,7 +491,7 @@ describe('ActiveApp — total ecosystem integration', () => { return { actorId: activeActorId, fetches }; } }); - await Permissions.check({ + await Perms.check({ action: PROJECT_ACTION_UPDATE, resource: { type: 'project', @@ -512,7 +512,7 @@ describe('ActiveApp — total ecosystem integration', () => { await drainMicrotasks(); expect(appIdentityEvents).toHaveLength(0); - expect(Permissions.size).toBe(1); + expect(Perms.size).toBe(1); expect(Chat.state).toBe(CONNECTION_STATE_OPEN); await expect( App.Cache.query({ @@ -535,7 +535,7 @@ describe('ActiveApp — total ecosystem integration', () => { await drainMicrotasks(); expect(appIdentityEvents).toHaveLength(1); - expect(Permissions.size).toBe(0); + expect(Perms.size).toBe(0); expect(Chat.state).toBe(CONNECTION_STATE_CLOSED); await expect( App.Cache.query({ @@ -572,8 +572,8 @@ describe('ActiveApp — total ecosystem integration', () => { role: activeRole, tenantIds: [TENANT_ID] }); - const permissionEngine = createPermissionEngine(); - const permissionHandlers = createPermissionHttpHandlers(permissionEngine, actorRef); + const permissionEngine = createPermEngine(); + const permissionHandlers = createPermHttpHandlers(permissionEngine, actorRef); const appFetch = (async (input: RequestInfo | URL, init?: RequestInit): Promise => { const path = requestPath(input); if (path.startsWith('/api/permissions')) { @@ -657,10 +657,10 @@ describe('ActiveApp — total ecosystem integration', () => { }); expect(JSON.stringify(appIdentityEvents[0])).not.toContain(TOKEN_ADA); - const Permissions = App.createActivePermissions({ - endpoint: PERMISSION_ENDPOINT, + const Perms = App.createActivePerms({ + endpoint: PERM_ENDPOINT, scopeKey: () => `${activeActorId}:${activeRole}`, - autoInvalidateOn: PERMISSION_AUTO_INVALIDATE_STANDARD + autoInvalidateOn: PERM_AUTO_INVALIDATE_STANDARD }); const Connections = App.createActiveConnections({ autoReauthOn: CONNECTION_AUTO_REAUTH_STANDARD @@ -696,13 +696,13 @@ describe('ActiveApp — total ecosystem integration', () => { await expect(pendingConnect).resolves.toMatchObject({ ok: true }); expect(Chat.state).toBe(CONNECTION_STATE_OPEN); - const adminDecision = await Permissions.check({ + const adminDecision = await Perms.check({ action: PROJECT_ACTION_UPDATE, resource, context: { risk: { mfa: true } } }); - expect(adminDecision.effect).toBe(PERMISSION_EFFECT_ALLOW); - expect(Permissions.size).toBe(1); + expect(adminDecision.effect).toBe(PERM_EFFECT_ALLOW); + expect(Perms.size).toBe(1); const cachedPresence = await App.Cache.query({ key: ['chat', 'presence'], @@ -740,7 +740,7 @@ describe('ActiveApp — total ecosystem integration', () => { }); await drainMicrotasks(); - expect(Permissions.size).toBe(0); + expect(Perms.size).toBe(0); expect(sessionEvents.some((event) => event.event === SESSION_EVENT_LIFECYCLE_ADOPTED)).toBe(true); expect(appIdentityEvents).toHaveLength(2); expect(appIdentityEvents[1]).toMatchObject({ @@ -776,19 +776,19 @@ describe('ActiveApp — total ecosystem integration', () => { false ); - const viewerDecision = await Permissions.check({ + const viewerDecision = await Perms.check({ action: PROJECT_ACTION_UPDATE, resource, context: { risk: { mfa: true } } }); - expect(viewerDecision.effect).toBe(PERMISSION_EFFECT_NOT_APPLICABLE); - expect(Permissions.size).toBe(1); + expect(viewerDecision.effect).toBe(PERM_EFFECT_NOT_APPLICABLE); + expect(Perms.size).toBe(1); await Sess.revoke(); await drainMicrotasks(); expect(Sess.current).toBeNull(); - expect(Permissions.size).toBe(0); + expect(Perms.size).toBe(0); expect(Chat.state).toBe(CONNECTION_STATE_CLOSED); expect(appIdentityEvents).toHaveLength(3); expect(appIdentityEvents[2]).toMatchObject({ @@ -822,7 +822,7 @@ describe('ActiveApp — total ecosystem integration', () => { } }); - it('propagates Auth sign-in and sign-out through Sess, Bus, Cache, Permissions and Connections', async () => { + it('propagates Auth sign-in and sign-out through Sess, Bus, Cache, Perms and Connections', async () => { let activeActorId = ANONYMOUS_ACTOR_ID; let activeRole = ROLE_VIEWER; let chatPresenceFetches = 0; @@ -839,8 +839,8 @@ describe('ActiveApp — total ecosystem integration', () => { role: activeRole, tenantIds: [TENANT_ID] }); - const permissionEngine = createPermissionEngine(); - const permissionHandlers = createPermissionHttpHandlers(permissionEngine, actorRef); + const permissionEngine = createPermEngine(); + const permissionHandlers = createPermHttpHandlers(permissionEngine, actorRef); const appFetch = (async (input: RequestInfo | URL, init?: RequestInit): Promise => { const path = requestPath(input); if (path.startsWith('/api/permissions')) { @@ -959,10 +959,10 @@ describe('ActiveApp — total ecosystem integration', () => { }); }); }); - const Permissions = App.createActivePermissions({ - endpoint: PERMISSION_ENDPOINT, + const Perms = App.createActivePerms({ + endpoint: PERM_ENDPOINT, scopeKey: () => `${activeActorId}:${activeRole}`, - autoInvalidateOn: PERMISSION_AUTO_INVALIDATE_STANDARD + autoInvalidateOn: PERM_AUTO_INVALIDATE_STANDARD }); const Connections = App.createActiveConnections({ autoReauthOn: CONNECTION_AUTO_REAUTH_STANDARD @@ -1009,13 +1009,13 @@ describe('ActiveApp — total ecosystem integration', () => { await expect(pendingConnect).resolves.toMatchObject({ ok: true }); await expect( - Permissions.check({ + Perms.check({ action: PROJECT_ACTION_UPDATE, resource, context: { risk: { mfa: true } } }) - ).resolves.toMatchObject({ effect: PERMISSION_EFFECT_ALLOW }); - expect(Permissions.size).toBe(1); + ).resolves.toMatchObject({ effect: PERM_EFFECT_ALLOW }); + expect(Perms.size).toBe(1); const adaPresence = await App.Cache.query({ key: ['auth', 'chat', 'presence'], @@ -1036,7 +1036,7 @@ describe('ActiveApp — total ecosystem integration', () => { expect(Auth.current.actor?.actorId).toBe(NEXT_ACTOR_ID); expect(Sess.current?.user?.id).toBe(NEXT_ACTOR_ID); - expect(Permissions.size).toBe(0); + expect(Perms.size).toBe(0); expect(appIdentityEvents).toHaveLength(2); expect(appIdentityEvents[1]).toMatchObject({ event: SESSION_EVENT_LIFECYCLE_ADOPTED, @@ -1066,13 +1066,13 @@ describe('ActiveApp — total ecosystem integration', () => { }) ).resolves.toEqual({ actorId: NEXT_ACTOR_ID, fetch: 2 }); await expect( - Permissions.check({ + Perms.check({ action: PROJECT_ACTION_UPDATE, resource, context: { risk: { mfa: true } } }) - ).resolves.toMatchObject({ effect: PERMISSION_EFFECT_NOT_APPLICABLE }); - expect(Permissions.size).toBe(1); + ).resolves.toMatchObject({ effect: PERM_EFFECT_NOT_APPLICABLE }); + expect(Perms.size).toBe(1); await Auth.signOut(); await authBridgeWork; @@ -1080,7 +1080,7 @@ describe('ActiveApp — total ecosystem integration', () => { expect(Auth.authenticated).toBe(false); expect(Sess.current).toBeNull(); - expect(Permissions.size).toBe(0); + expect(Perms.size).toBe(0); expect(Chat.state).toBe(CONNECTION_STATE_CLOSED); expect(appIdentityEvents).toHaveLength(3); expect(appIdentityEvents[2]).toMatchObject({ @@ -1125,8 +1125,8 @@ describe('ActiveApp — total ecosystem integration', () => { let activeActorId = ANONYMOUS_ACTOR_ID; let activeRole = ROLE_VIEWER; let chatPresenceFetches = 0; - let delayNextPermission = false; - const stalePermission = createDeferred>(); + let delayNextPerm = false; + const stalePerm = createDeferred>(); const stalePresence = createDeferred<{ readonly actorId: string; readonly fetch: number }>(); const resource: ProjectResource = { type: 'project', @@ -1140,15 +1140,15 @@ describe('ActiveApp — total ecosystem integration', () => { role: activeRole, tenantIds: [TENANT_ID] }); - const permissionEngine = createPermissionEngine(); - const permissionHandlers = createPermissionHttpHandlers(permissionEngine, actorRef); + const permissionEngine = createPermEngine(); + const permissionHandlers = createPermHttpHandlers(permissionEngine, actorRef); const appFetch = (async (input: RequestInfo | URL, init?: RequestInit): Promise => { const path = requestPath(input); if (path.startsWith('/api/permissions')) { const body = init?.body === undefined ? {} : JSON.parse(String(init.body)); - if (delayNextPermission) { - delayNextPermission = false; - return jsonResponse(await stalePermission.promise); + if (delayNextPerm) { + delayNextPerm = false; + return jsonResponse(await stalePerm.promise); } const request = { method: init?.method ?? 'POST', @@ -1243,10 +1243,10 @@ describe('ActiveApp — total ecosystem integration', () => { }); }); }); - const Permissions = App.createActivePermissions({ - endpoint: PERMISSION_ENDPOINT, + const Perms = App.createActivePerms({ + endpoint: PERM_ENDPOINT, scopeKey: () => `${activeActorId}:${activeRole}`, - autoInvalidateOn: PERMISSION_AUTO_INVALIDATE_STANDARD + autoInvalidateOn: PERM_AUTO_INVALIDATE_STANDARD }); const Connections = App.createActiveConnections({ autoReauthOn: CONNECTION_AUTO_REAUTH_STANDARD @@ -1278,8 +1278,8 @@ describe('ActiveApp — total ecosystem integration', () => { ackFrame(transport, latestFrame(transport)); await expect(pendingConnect).resolves.toMatchObject({ ok: true }); - delayNextPermission = true; - const oldPermission = Permissions.check({ + delayNextPerm = true; + const oldPerm = Perms.check({ action: PROJECT_ACTION_UPDATE, resource, context: { risk: { mfa: true } } @@ -1299,7 +1299,7 @@ describe('ActiveApp — total ecosystem integration', () => { await drainMicrotasks(); expect(Sess.current?.user?.id).toBe(NEXT_ACTOR_ID); - expect(Permissions.size).toBe(0); + expect(Perms.size).toBe(0); await waitForSentCount(transport, 2); const nextAuth = latestFrame(transport); expect(nextAuth).toMatchObject({ @@ -1308,28 +1308,28 @@ describe('ActiveApp — total ecosystem integration', () => { }); ackFrame(transport, nextAuth); - stalePermission.resolve({ - effect: PERMISSION_EFFECT_ALLOW, + stalePerm.resolve({ + effect: PERM_EFFECT_ALLOW, reason: 'stale-ada-response', ttl: 60_000 }); stalePresence.resolve({ actorId: ACTOR_ID, fetch: 1 }); - await expect(oldPermission).resolves.toMatchObject({ - effect: PERMISSION_EFFECT_ALLOW, + await expect(oldPerm).resolves.toMatchObject({ + effect: PERM_EFFECT_ALLOW, reason: 'stale-ada-response' }); await expect(oldPresence).resolves.toEqual({ actorId: ACTOR_ID, fetch: 1 }); - expect(Permissions.size).toBe(0); + expect(Perms.size).toBe(0); await expect( - Permissions.check({ + Perms.check({ action: PROJECT_ACTION_UPDATE, resource, context: { risk: { mfa: true } } }) - ).resolves.toMatchObject({ effect: PERMISSION_EFFECT_NOT_APPLICABLE }); - expect(Permissions.size).toBe(1); + ).resolves.toMatchObject({ effect: PERM_EFFECT_NOT_APPLICABLE }); + expect(Perms.size).toBe(1); await expect( App.Cache.query({ @@ -1357,14 +1357,14 @@ describe('ActiveApp — total ecosystem integration', () => { const activeRole = ROLE_ADMIN; let dashboardFetches = 0; const cacheAdapter = memoryCacheAdapter({ suppressProductionWarning: true }); - const permissionEngine = createPermissionEngine(); + const permissionEngine = createPermEngine(); const actorRef = (): SubjectRef => ({ type: 'user', id: activeActorId, role: activeRole, tenantIds: [activeTenantId] }); - const permissionHandlers = createPermissionHttpHandlers(permissionEngine, actorRef); + const permissionHandlers = createPermHttpHandlers(permissionEngine, actorRef); const resourceForTenant = (): ProjectResource => ({ type: 'project', id: PROJECT_ID, @@ -1429,10 +1429,10 @@ describe('ActiveApp — total ecosystem integration', () => { issuedAt: 1, expiresAt: Date.now() + 60_000 }); - const Permissions = App.createActivePermissions({ - endpoint: PERMISSION_ENDPOINT, + const Perms = App.createActivePerms({ + endpoint: PERM_ENDPOINT, scopeKey: () => `${activeTenantId}:${activeActorId}:${activeRole}`, - autoInvalidateOn: PERMISSION_AUTO_INVALIDATE_STANDARD + autoInvalidateOn: PERM_AUTO_INVALIDATE_STANDARD }); const Connections = App.createActiveConnections({ autoReauthOn: CONNECTION_AUTO_REAUTH_STANDARD @@ -1459,13 +1459,13 @@ describe('ActiveApp — total ecosystem integration', () => { expect(transport.sentMessages()).toHaveLength(1); await expect( - Permissions.check({ + Perms.check({ action: PROJECT_ACTION_UPDATE, resource: resourceForTenant(), context: { risk: { mfa: true } } }) - ).resolves.toMatchObject({ effect: PERMISSION_EFFECT_ALLOW }); - expect(Permissions.size).toBe(1); + ).resolves.toMatchObject({ effect: PERM_EFFECT_ALLOW }); + expect(Perms.size).toBe(1); const firstDashboard = await App.Cache.query({ key: ['tenant', 'dashboard'], @@ -1489,13 +1489,13 @@ describe('ActiveApp — total ecosystem integration', () => { }); expect(cacheAdapter.inspect().entries).toHaveLength(1); - publishAppPermissionsRefreshRequested(App.Bus, { + publishAppPermsRefreshRequested(App.Bus, { cause: 'policy-published', generation: 2 }); await drainMicrotasks(); - expect(Permissions.size).toBe(0); + expect(Perms.size).toBe(0); expect(transport.sentMessages()).toHaveLength(1); expect(cacheAdapter.inspect().entries).toHaveLength(1); await expect( @@ -1512,13 +1512,13 @@ describe('ActiveApp — total ecosystem integration', () => { ).resolves.toEqual(firstDashboard); await expect( - Permissions.check({ + Perms.check({ action: PROJECT_ACTION_UPDATE, resource: resourceForTenant(), context: { risk: { mfa: true } } }) - ).resolves.toMatchObject({ effect: PERMISSION_EFFECT_ALLOW }); - expect(Permissions.size).toBe(1); + ).resolves.toMatchObject({ effect: PERM_EFFECT_ALLOW }); + expect(Perms.size).toBe(1); activeTenantId = SECOND_TENANT_ID; publishAppTenantSwitched(App.Bus, { @@ -1528,7 +1528,7 @@ describe('ActiveApp — total ecosystem integration', () => { }); await drainMicrotasks(); - expect(Permissions.size).toBe(0); + expect(Perms.size).toBe(0); expect(transport.sentMessages()).toHaveLength(1); expect(cacheAdapter.inspect().entries).toHaveLength(0); const secondTenantDashboard = await App.Cache.query({ @@ -1589,19 +1589,19 @@ describe('ActiveApp — total ecosystem integration', () => { const activeActorId = ACTOR_ID; let activeRole = ROLE_ADMIN; let dashboardFetches = 0; - let delayNextPermission = false; + let delayNextPerm = false; const permissionRefreshCauses: string[] = []; const permissionRefreshPayloads: unknown[] = []; - const delayedPermission = createDeferred>(); + const delayedPerm = createDeferred>(); const cacheAdapter = memoryCacheAdapter({ suppressProductionWarning: true }); - const permissionEngine = createPermissionEngine(); + const permissionEngine = createPermEngine(); const actorRef = (): SubjectRef => ({ type: 'user', id: activeActorId, role: activeRole, tenantIds: [activeTenantId] }); - const permissionHandlers = createPermissionHttpHandlers(permissionEngine, actorRef); + const permissionHandlers = createPermHttpHandlers(permissionEngine, actorRef); const resource: ProjectResource = { type: 'project', id: PROJECT_ID, @@ -1612,9 +1612,9 @@ describe('ActiveApp — total ecosystem integration', () => { const path = requestPath(input); if (path.startsWith('/api/permissions')) { const body = init?.body === undefined ? {} : JSON.parse(String(init.body)); - if (delayNextPermission) { - delayNextPermission = false; - return jsonResponse(await delayedPermission.promise); + if (delayNextPerm) { + delayNextPerm = false; + return jsonResponse(await delayedPerm.promise); } const request = { method: init?.method ?? 'POST', @@ -1658,7 +1658,7 @@ describe('ActiveApp — total ecosystem integration', () => { } } }); - const offPermissionRefresh = onAppPermissionsRefreshRequested(App.Bus, (event) => { + const offPermRefresh = onAppPermsRefreshRequested(App.Bus, (event) => { permissionRefreshCauses.push(event.payload.cause); permissionRefreshPayloads.push(event.payload); }); @@ -1675,10 +1675,10 @@ describe('ActiveApp — total ecosystem integration', () => { issuedAt: 1, expiresAt: Date.now() + 60_000 }); - const Permissions = App.createActivePermissions({ - endpoint: PERMISSION_ENDPOINT, + const Perms = App.createActivePerms({ + endpoint: PERM_ENDPOINT, scopeKey: () => `${activeTenantId}:${activeActorId}:${activeRole}`, - autoInvalidateOn: PERMISSION_AUTO_INVALIDATE_STANDARD + autoInvalidateOn: PERM_AUTO_INVALIDATE_STANDARD }); const Connections = App.createActiveConnections({ autoReauthOn: CONNECTION_AUTO_REAUTH_STANDARD @@ -1698,12 +1698,12 @@ describe('ActiveApp — total ecosystem integration', () => { session: { enabled: true, reauthOnChange: true, reauthOnRefresh: true } }); offChatWebhook = Chat.onAny((frame) => { - if (frame.type !== BACKEND_PERMISSION_CHANGED_FRAME_TYPE) return; + if (frame.type !== BACKEND_PERM_CHANGED_FRAME_TYPE) return; const payload = frame.payload as { generation?: unknown } | undefined; const generation = typeof payload?.generation === 'number' ? payload.generation : undefined; - void publishAppPermissionsRefreshRequested(App.Bus, { - cause: BACKEND_PERMISSION_CHANGED_CAUSE, + void publishAppPermsRefreshRequested(App.Bus, { + cause: BACKEND_PERM_CHANGED_CAUSE, generation }); }); @@ -1715,13 +1715,13 @@ describe('ActiveApp — total ecosystem integration', () => { expect(transport.sentMessages()).toHaveLength(1); await expect( - Permissions.check({ + Perms.check({ action: PROJECT_ACTION_UPDATE, resource, context: { risk: { mfa: true } } }) - ).resolves.toMatchObject({ effect: PERMISSION_EFFECT_ALLOW }); - expect(Permissions.size).toBe(1); + ).resolves.toMatchObject({ effect: PERM_EFFECT_ALLOW }); + expect(Perms.size).toBe(1); const cachedDashboard = await App.Cache.query({ key: ['backend-webhook', 'dashboard'], @@ -1746,8 +1746,8 @@ describe('ActiveApp — total ecosystem integration', () => { tenantId: activeTenantId, locked: false }; - delayNextPermission = true; - const stalePermission = Permissions.check({ + delayNextPerm = true; + const stalePerm = Perms.check({ action: PROJECT_ACTION_UPDATE, resource: delayedResource, context: { risk: { mfa: true } } @@ -1758,23 +1758,23 @@ describe('ActiveApp — total ecosystem integration', () => { transport.emitMessage( JSON.stringify( createFrame({ - type: BACKEND_PERMISSION_CHANGED_FRAME_TYPE, + type: BACKEND_PERM_CHANGED_FRAME_TYPE, payload: { generation: 2 } }) ) ); await drainMicrotasks(); - delayedPermission.resolve({ - effect: PERMISSION_EFFECT_ALLOW, + delayedPerm.resolve({ + effect: PERM_EFFECT_ALLOW, reason: 'stale-pre-webhook-permission', ttl: 60_000 }); - expect(permissionRefreshCauses).toEqual([BACKEND_PERMISSION_CHANGED_CAUSE]); - expect(Permissions.size).toBe(0); + expect(permissionRefreshCauses).toEqual([BACKEND_PERM_CHANGED_CAUSE]); + expect(Perms.size).toBe(0); expect(transport.sentMessages()).toHaveLength(1); expect(cacheAdapter.inspect().entries).toHaveLength(1); - expect(JSON.stringify(decodedFrames(transport))).not.toContain(BACKEND_PERMISSION_CHANGED_CAUSE); + expect(JSON.stringify(decodedFrames(transport))).not.toContain(BACKEND_PERM_CHANGED_CAUSE); expect(JSON.stringify(permissionRefreshPayloads)).not.toContain(TOKEN_ADA); await expect( App.Cache.query({ @@ -1788,23 +1788,23 @@ describe('ActiveApp — total ecosystem integration', () => { } }) ).resolves.toEqual(cachedDashboard); - await expect(stalePermission).resolves.toMatchObject({ - effect: PERMISSION_EFFECT_ALLOW, + await expect(stalePerm).resolves.toMatchObject({ + effect: PERM_EFFECT_ALLOW, reason: 'stale-pre-webhook-permission' }); - expect(Permissions.size).toBe(0); + expect(Perms.size).toBe(0); await expect( - Permissions.check({ + Perms.check({ action: PROJECT_ACTION_UPDATE, resource: delayedResource, context: { risk: { mfa: true } } }) - ).resolves.toMatchObject({ effect: PERMISSION_EFFECT_NOT_APPLICABLE }); - expect(Permissions.size).toBe(1); + ).resolves.toMatchObject({ effect: PERM_EFFECT_NOT_APPLICABLE }); + expect(Perms.size).toBe(1); } finally { offChatWebhook?.(); - offPermissionRefresh.unsubscribe(); + offPermRefresh.unsubscribe(); App.dispose(); permissionEngine.dispose(); } @@ -1818,14 +1818,14 @@ describe('ActiveApp — total ecosystem integration', () => { let remoteRevokeWork: Promise = Promise.resolve(); const appIdentityEvents: unknown[] = []; const cacheAdapter = memoryCacheAdapter({ suppressProductionWarning: true }); - const permissionEngine = createPermissionEngine(); + const permissionEngine = createPermEngine(); const actorRef = (): SubjectRef => ({ type: 'user', id: activeActorId, role: activeRole, tenantIds: [activeTenantId] }); - const permissionHandlers = createPermissionHttpHandlers(permissionEngine, actorRef); + const permissionHandlers = createPermHttpHandlers(permissionEngine, actorRef); const resource: ProjectResource = { type: 'project', id: PROJECT_ID, @@ -1894,10 +1894,10 @@ describe('ActiveApp — total ecosystem integration', () => { issuedAt: 1, expiresAt: Date.now() + 60_000 }); - const Permissions = App.createActivePermissions({ - endpoint: PERMISSION_ENDPOINT, + const Perms = App.createActivePerms({ + endpoint: PERM_ENDPOINT, scopeKey: () => `${activeTenantId}:${activeActorId}:${activeRole}`, - autoInvalidateOn: PERMISSION_AUTO_INVALIDATE_STANDARD + autoInvalidateOn: PERM_AUTO_INVALIDATE_STANDARD }); const Connections = App.createActiveConnections({ autoReauthOn: CONNECTION_AUTO_REAUTH_STANDARD @@ -1938,13 +1938,13 @@ describe('ActiveApp — total ecosystem integration', () => { expect(Chat.state).toBe(CONNECTION_STATE_OPEN); await expect( - Permissions.check({ + Perms.check({ action: PROJECT_ACTION_UPDATE, resource, context: { risk: { mfa: true } } }) - ).resolves.toMatchObject({ effect: PERMISSION_EFFECT_ALLOW }); - expect(Permissions.size).toBe(1); + ).resolves.toMatchObject({ effect: PERM_EFFECT_ALLOW }); + expect(Perms.size).toBe(1); await expect( App.Cache.query({ @@ -1973,7 +1973,7 @@ describe('ActiveApp — total ecosystem integration', () => { await drainMicrotasks(); expect(Sess.current).toBeNull(); - expect(Permissions.size).toBe(0); + expect(Perms.size).toBe(0); expect(Chat.state).toBe(CONNECTION_STATE_CLOSED); expect(cacheAdapter.inspect().entries).toHaveLength(0); expect(appIdentityEvents).toHaveLength(2); @@ -2008,7 +2008,7 @@ describe('ActiveApp — total ecosystem integration', () => { }); }); -function createPermissionEngine() { +function createPermEngine() { const schema = definePermSchema({ actors: { user: { @@ -2035,7 +2035,7 @@ function createPermissionEngine() { } }); - return createEnginePermissions({ + return createEnginePerms({ schema, policies: definePolicies(schema, [ allow(PROJECT_ACTION_UPDATE).when( diff --git a/src/arts/active-app/test/test-app.test.ts b/src/arts/active-app/test/test-app.test.ts index 3a27f8a..1cafc54 100644 --- a/src/arts/active-app/test/test-app.test.ts +++ b/src/arts/active-app/test/test-app.test.ts @@ -22,7 +22,7 @@ describe('createTestApp', () => { const App = createTestApp(); expect(App.Logger).toBeDefined(); expect(App.Lang).toBeDefined(); - expect(App.Formats).toBeDefined(); + expect(App.Format).toBeDefined(); expect(App.Frontend).toBeDefined(); expect(App.Dom).toBeDefined(); expect(App.entries).toEqual([]); @@ -111,7 +111,7 @@ describe('createTestApp', () => { }); expect(App.Lang.t('greeting')).toBe('Hola'); - expect(App.Formats.currency.getCurrency()).toBe('EUR'); + expect(App.Format.currency.getCurrency()).toBe('EUR'); App.setLocale('en'); expect(App.Lang.t('greeting')).toBe('Hello'); diff --git a/src/arts/active-app/types.ts b/src/arts/active-app/types.ts index 84758d3..2b02fef 100644 --- a/src/arts/active-app/types.ts +++ b/src/arts/active-app/types.ts @@ -5,12 +5,12 @@ import type { ActiveCache, ActiveCacheOptions } from '$cache'; import type { ActiveConnections, ActiveConnectionsOptions, ConnectionMap } from '$connection'; import type { ActiveFrontend, ActiveFrontendOptions } from '$frontend'; import type { FrontendPreferenceKey } from '$frontend'; -import type { ActiveFormats, ActiveFormatsOptions } from '$formats'; +import type { ActiveFormat, ActiveFormatOptions } from '$format'; import type { EngineHttp, EngineHttpOptions } from '$http'; import type { AppEventMap } from '$libs/active-app/events'; import type { ActiveLang, LangNode, SupportedLocale } from '$libs/lang'; import type { EngineLogger, LoggerOptions } from '$logger'; -import type { ActivePermissions, ActivePermissionsOptions } from '$permissions'; +import type { ActivePerms, ActivePermsOptions } from '$perm'; import type { ActiveSession, EngineSessionOptions, SessEventMap } from '$session'; import type { EngineSium } from '$sium'; import type { ActiveStorage, SyncStorageAdapter } from '$storage'; @@ -22,7 +22,7 @@ import type { APP_ORCHESTRATION_TRANSLATOR_CONNECTIVITY, APP_ORCHESTRATION_TRANSLATOR_DISPOSE, APP_ORCHESTRATION_TRANSLATOR_IDENTITY, - APP_ORCHESTRATION_TRANSLATOR_PERMISSION_REFRESH, + APP_ORCHESTRATION_TRANSLATOR_PERM_REFRESH, APP_ORCHESTRATION_TRANSLATOR_TENANT_SWITCHED } from './consts.ts'; @@ -78,7 +78,7 @@ export type ActiveAppOrchestrationPreset = export type ActiveAppOrchestrationTranslator = | typeof APP_ORCHESTRATION_TRANSLATOR_IDENTITY - | typeof APP_ORCHESTRATION_TRANSLATOR_PERMISSION_REFRESH + | typeof APP_ORCHESTRATION_TRANSLATOR_PERM_REFRESH | typeof APP_ORCHESTRATION_TRANSLATOR_TENANT_SWITCHED | typeof APP_ORCHESTRATION_TRANSLATOR_CONNECTIVITY | typeof APP_ORCHESTRATION_TRANSLATOR_DISPOSE; @@ -94,9 +94,9 @@ export type ActiveAppOrchestrationOptions = * - `logger` defaults to the engine's `level: WARN` + `consoleTransport()`. * Pass `{ level: LogLevel.NONE, transports: [] }` for silence. * - `lang` defaults to a mono lang when absent (single-language passthrough, - * reactive locale still owned so Formats/Frontend stay in sync). + * reactive locale still owned so Format/Frontend stay in sync). * - `formats` is always built; sub-engines accept their own knobs (currency, - * date order, etc.). When absent the locale falls back to `FORMATS_DEFAULT_LOCALE`. + * date order, etc.). When absent the locale falls back to `FORMAT_DEFAULT_LOCALE`. * * Sium is intentionally **not** part of App — validation is page-scoped. * Pages that need it construct an `EngineSium` directly: @@ -113,7 +113,7 @@ export interface ActiveAppOptions { defaultLocale?: SupportedLocale; fallbackChain?: SupportedLocale[]; }; - formats?: Omit; + formats?: Omit; frontend?: Omit & { /** * Persist user preferences (theme, mode, density, ...) through @@ -163,12 +163,12 @@ export interface ActiveAppOptions { */ connections?: Omit; /** - * Defaults for `App.createActivePermissions()`. App injects Logger, Http + * Defaults for `App.createActivePerms()`. App injects Logger, Http * and Bus automatically; the endpoint remains explicit because the client * only reflects server decisions. Automatic app-event invalidation is * controlled by `autoInvalidateOn`. */ - permissions?: Omit; + permissions?: Omit; /** * Defaults for `App.createActiveAuth()`. App injects Http and Cache * automatically; the authoritative auth engine still lives server-side @@ -189,7 +189,7 @@ export interface ActiveAppOptions { * Composed application surface. * * Every member is **always** present so call sites can use `App.Lang.t(...)` - * or `App.Formats.currency.format(...)` without null checks. When the caller + * or `App.Format.currency.format(...)` without null checks. When the caller * did not configure an artifact, App provides a structurally identical * adapter: * @@ -197,14 +197,14 @@ export interface ActiveAppOptions { * | -------- | ---------- | ------------------------------------------------ | * | Logger | real | `level: WARN` + `consoleTransport()` (engine default; pass `{ level: NONE, transports: [] }` for silence) | * | Lang | real | mono — returns paths and `\|fallback` literals; warns once per path in DEV via Logger under `lang.mono` | - * | Formats | real | real with locale = `FORMATS_DEFAULT_LOCALE` (`'en-US'`) | + * | Format | real | real with locale = `FORMAT_DEFAULT_LOCALE` (`'en-US'`) | * | Frontend | real | real with default theme/mode/density | * | Dom | real | real with default breakpoints | */ export interface ActiveApp { readonly Logger: EngineLogger; readonly Lang: ActiveLang; - readonly Formats: ActiveFormats; + readonly Format: ActiveFormat; readonly Frontend: ActiveFrontend; readonly Dom: ActiveDom; readonly Storage: ActiveStorage; @@ -286,12 +286,12 @@ export interface ActiveApp { /** * Build the App-scoped reactive permissions client. The authoritative - * runtime is `createEnginePermissions()` from `$svrs/permissions`; this client + * runtime is `createEnginePerms()` from `$svrs/perm`; this client * is only for UI/UX reflection, snapshots and cache. */ - createActivePermissions( - options?: Partial> - ): ActivePermissions; + createActivePerms( + options?: Partial> + ): ActivePerms; /** * Build the App-scoped active authentication client. The server-side @@ -307,7 +307,7 @@ export interface ActiveApp { * with `if (App.Sess) { ... }`. */ readonly Sess: ActiveSession | undefined; - readonly Permissions: ActivePermissions | undefined; + readonly Perms: ActivePerms | undefined; readonly Auth: ActiveAuth | undefined; /** Tear down owned instances in reverse construction order. Idempotent. */ diff --git a/src/arts/auth/README.md b/src/arts/auth/README.md index bfc53ce..62d122b 100644 --- a/src/arts/auth/README.md +++ b/src/arts/auth/README.md @@ -324,7 +324,7 @@ deviceId; `perm` puede usar ese contexto para decidir: ```ts -Permissions.can({ +Perms.can({ actor, action: 'project:update', resource diff --git a/src/arts/bus/README.md b/src/arts/bus/README.md index 0d753f5..fadebe8 100644 --- a/src/arts/bus/README.md +++ b/src/arts/bus/README.md @@ -368,7 +368,7 @@ const Bus = createSvelteEngineBus({ clock: Timers.clock }); -const App = { Logger, Lang, Formats, Frontend, Dom, Storage, Http, Timers, Bus, Cache }; +const App = { Logger, Lang, Format, Frontend, Dom, Storage, Http, Timers, Bus, Cache }; ``` Modules accept `EngineBus` or `EventPublisher` from `$buss` and use @@ -676,7 +676,7 @@ const Cache = App.createActiveCache({ autoInvalidateOn: 'standard' }); -const Permissions = App.createActivePermissions({ +const Perms = App.createActivePerms({ endpoint: '/permissions', autoInvalidateOn: 'standard' }); @@ -691,7 +691,7 @@ Defaults for `'standard'`: | Consumer | Reacts to (`'standard'`) | Action | | --------------- | ----------------------------------------------------------------------- | ---------------------------- | | `Cache` | `userIdentityChanged`, `tenantSwitched` | `Cache.invalidate()` | -| `Permissions` | `userIdentityChanged`, `permissionsRefresh`, `tenantSwitched` | `Permissions.invalidate()` | +| `Perms` | `userIdentityChanged`, `permissionsRefresh`, `tenantSwitched` | `Perms.invalidate()` | | `Connections` | `userIdentityChanged` | `reauthenticateAll()` | Override values: `'standard'` (table above), `'none'` (no auto-reactions), @@ -894,7 +894,7 @@ Implemented: - `libs/aapp/events.ts` app-event constants, runtime metadata, `publishApp*` helpers, `onApp*` helpers and unsafe-payload guard. - App-level session translator and dispose-starting publisher. -- Per-consumer reactions for Cache, Permissions and Connections. +- Per-consumer reactions for Cache, Perms and Connections. ## Deferred work @@ -915,7 +915,7 @@ Out of scope for the current cut: - `BUS_*` — generic bus constants (live in `arts/buss`). - `APP_EVENT_*` — public app event names (live in `libs/aapp/events.ts`). - `_EVENT_*` (`SESSION_EVENT_*`, `AUTH_EVENT_*`, `CACHE_EVENT_*`, - `PERMISSION_EVENT_*`, `CONNECTION_EVENT_*`) — module event names, live in their + `PERM_EVENT_*`, `CONNECTION_EVENT_*`) — module event names, live in their owning artifact's `consts.ts`. - Diagnostic constants are **full-prefixed strings**, never bare names: `'bus.event.published'`, not `'event_published'`. diff --git a/src/arts/cache/README.md b/src/arts/cache/README.md index f2c0fd4..f9a207f 100644 --- a/src/arts/cache/README.md +++ b/src/arts/cache/README.md @@ -72,7 +72,7 @@ const App = createActiveApp({ scopeResolver: () => ({ tenantId: App.Sess?.current?.data?.tenantId, actorId: App.Sess?.current?.user?.id, - permissionHash: App.Permissions?.currentSnapshot.version, + permissionHash: App.Perms?.currentSnapshot.version, locale: App.getLocale() }) } diff --git a/src/arts/formats/README.md b/src/arts/format/README.md similarity index 85% rename from src/arts/formats/README.md rename to src/arts/format/README.md index da003aa..6cbc43f 100644 --- a/src/arts/formats/README.md +++ b/src/arts/format/README.md @@ -1,6 +1,6 @@ -# Formats +# Format -`Formats` es la API publica del artefacto `fmts`. Su responsabilidad es +`Format` es la API publica del artefacto `fmts`. Su responsabilidad es centralizar todos los formatos que dependen de `locale`: numeros, moneda, unidades y fechas. No traduce textos, no decide idioma y no depende de `Lang`; solo consume una fuente de locale cuando se quiere reactividad. @@ -10,17 +10,17 @@ El objetivo es que una app tenga una sola verdad: ```ts App.setLocale('es-AR'); -App.Formats.numbers.format(1234.5); -App.Formats.currency.getCurrency(); // ARS -App.Formats.units.getSystem(); // metric -App.Formats.dates.getDateOrder(); +App.Format.numbers.format(1234.5); +App.Format.currency.getCurrency(); // ARS +App.Format.units.getSystem(); // metric +App.Format.dates.getDateOrder(); ``` ## Mapa del modulo | Pieza | Factory | Responsabilidad | | --- | --- | --- | -| `formats` | `createEngineFormats`, `createActiveFormats` | Agrega `numbers`, `currency`, `units` y `dates` bajo un mismo locale. | +| `formats` | `createEngineFormat`, `createActiveFormat` | Agrega `numbers`, `currency`, `units` y `dates` bajo un mismo locale. | | `numbers` | `createEngineNumbers`, `createActiveNumbers` | Formato y parseo numerico, separadores, porcentajes, compact, currency/unit via `Intl.NumberFormat`. | | `currency` | `createEngineCurrency`, `createActiveCurrency` | Moneda por region, formato de moneda, conversion y cache de rates. | | `units` | `createEngineUnits`, `createActiveUnits` | Sistema metrico/imperial, unidades por defecto y conversiones. | @@ -37,9 +37,9 @@ Todos los submodulos siguen el mismo patron: ## Uso rapido ```ts -import { createEngineFormats } from '$fmts'; +import { createEngineFormat } from '$fmts'; -const formats = createEngineFormats({ locale: 'es-ES' }); +const formats = createEngineFormat({ locale: 'es-ES' }); formats.numbers.format(1234.5); // "1234,5" segun Intl del runtime formats.currency.format(12.5); // "12,50 EUR" @@ -50,22 +50,22 @@ formats.dates.formatDateTime(new Date()); En una app Svelte: ```ts -import { createActiveFormats } from '$fmts'; +import { createActiveFormat } from '$fmts'; -const Formats = createActiveFormats({ +const Format = createActiveFormat({ locale: 'en-US' }); -Formats.setLocale('fr-FR'); -Formats.currency.format(99.5); +Format.setLocale('fr-FR'); +Format.currency.format(99.5); ``` -Bajo `createActiveApp(...)` normalmente no se crea a mano: `App.Formats` recibe +Bajo `createActiveApp(...)` normalmente no se crea a mano: `App.Format` recibe el `localeSource` de la app y se mueve con `App.setLocale(...)`. ## LocaleSource -`Formats` consume el alias compartido `LocaleSource` de `$locale`: +`Format` consume el alias compartido `LocaleSource` de `$locale`: ```ts import type { LocaleSource } from '$locale'; @@ -76,7 +76,7 @@ interface LocaleSource { } ``` -`createActiveFormats({ localeSource })` suscribe esa fuente y propaga los cambios +`createActiveFormat({ localeSource })` suscribe esa fuente y propaga los cambios a todos los submotores. Si el source no tiene `onLocaleChange`, el engine puede leer `getLocale()` cuando se formatea, pero no recibe notificaciones activas. @@ -175,7 +175,7 @@ const rates = createRates({ } }); -const formats = createEngineFormats({ +const formats = createEngineFormat({ locale: 'es-ES', currency: { rates } }); @@ -233,15 +233,15 @@ Los wrappers activos exponen listeners para que una UI o un modulo superior pueda reaccionar sin inspeccionar internals: ```ts -const offLocale = Formats.numbers.onLocaleChange((locale) => { +const offLocale = Format.numbers.onLocaleChange((locale) => { console.log('locale changed', locale); }); -const offPrefs = Formats.dates.onPreferenceChange(() => { +const offPrefs = Format.dates.onPreferenceChange(() => { console.log('date preferences changed'); }); -const offCurrency = Formats.currency.onCurrencyChange((currency) => { +const offCurrency = Format.currency.onCurrencyChange((currency) => { console.log('currency changed', currency); }); ``` @@ -250,7 +250,7 @@ const offCurrency = Formats.currency.onCurrencyChange((currency) => { ## Integracion recomendada -En `aapp`, `Formats` debe compartir locale con `Lang` y `Frontend`: +En `aapp`, `Format` debe compartir locale con `Lang` y `Frontend`: ```ts const App = createActiveApp({ @@ -261,13 +261,13 @@ const App = createActiveApp({ }); App.setLocale('ar'); -App.Formats.dates.formatDate(new Date()); +App.Format.dates.formatDate(new Date()); ``` Si un modulo necesita solo formatos sin toda la app, usa el engine aislado: ```ts -const formats = createEngineFormats({ +const formats = createEngineFormat({ locale: () => requestLocale }); ``` @@ -278,5 +278,5 @@ Tests utiles: - `npx vitest run src/arts/fmts` - `/test/fmts` para validar locale compartido, auto/manual y UI. -- `/test/ecosystem` para validar `Lang + Formats + Frontend + Dom` juntos. +- `/test/ecosystem` para validar `Lang + Format + Frontend + Dom` juntos. diff --git a/src/arts/formats/active-formats.svelte.ts b/src/arts/format/active-formats.svelte.ts similarity index 82% rename from src/arts/formats/active-formats.svelte.ts rename to src/arts/format/active-formats.svelte.ts index 7679272..1be92bd 100644 --- a/src/arts/formats/active-formats.svelte.ts +++ b/src/arts/format/active-formats.svelte.ts @@ -2,23 +2,23 @@ import { createActiveCurrency } from './currency/active-currency.svelte'; import { createActiveDates } from './dates/active-dates.svelte'; import { createActiveNumbers } from './numbers/active-numbers.svelte'; import { createActiveUnits } from './units/active-units.svelte'; -import { createActiveFormatsLocaleSource } from './active-runtime.svelte'; +import { createActiveFormatLocaleSource } from './active-runtime.svelte'; import type { ActiveCurrency, ActiveCurrencyOptions } from './currency'; import type { ActiveDates, ActiveDatesOptions } from './dates'; import type { ActiveNumbers, ActiveNumbersOptions } from './numbers'; import type { ActiveUnits, ActiveUnitsOptions } from './units'; -import type { FormatsLocaleSource } from './types'; +import type { FormatLocaleSource } from './types'; -export interface ActiveFormatsOptions { +export interface ActiveFormatOptions { locale?: string; - localeSource?: FormatsLocaleSource; + localeSource?: FormatLocaleSource; numbers?: Omit; currency?: Omit; units?: Omit; dates?: Omit; } -export interface ActiveFormats { +export interface ActiveFormat { readonly numbers: ActiveNumbers; readonly currency: ActiveCurrency; readonly units: ActiveUnits; @@ -28,8 +28,8 @@ export interface ActiveFormats { dispose: () => void; } -export function createActiveFormats(options: ActiveFormatsOptions = {}): ActiveFormats { - const localeState = createActiveFormatsLocaleSource(options); +export function createActiveFormat(options: ActiveFormatOptions = {}): ActiveFormat { + const localeState = createActiveFormatLocaleSource(options); const localeSource = localeState.source; const numbers = createActiveNumbers({ ...options.numbers, localeSource }); diff --git a/src/arts/formats/active-runtime.svelte.ts b/src/arts/format/active-runtime.svelte.ts similarity index 74% rename from src/arts/formats/active-runtime.svelte.ts rename to src/arts/format/active-runtime.svelte.ts index 4fb8d5c..efe6db5 100644 --- a/src/arts/formats/active-runtime.svelte.ts +++ b/src/arts/format/active-runtime.svelte.ts @@ -1,23 +1,23 @@ -import type { FormatsLocaleSource } from './types'; +import type { FormatLocaleSource } from './types'; -export interface ActiveFormatsLocaleSourceOptions { +export interface ActiveFormatLocaleSourceOptions { locale?: string; - localeSource?: FormatsLocaleSource; + localeSource?: FormatLocaleSource; } -export interface ActiveFormatsLocaleSource { - readonly source: FormatsLocaleSource; +export interface ActiveFormatLocaleSource { + readonly source: FormatLocaleSource; getLocale: () => string; setLocale: (locale: string) => void; } -export interface ActiveFormatsRuntimeOptions { - localeSource?: FormatsLocaleSource; +export interface ActiveFormatRuntimeOptions { + localeSource?: FormatLocaleSource; getLocale: () => string; setLocale: (locale: string) => void; } -export interface ActiveFormatsRuntime { +export interface ActiveFormatRuntime { read: () => void; notifyChange: () => void; syncLocale: (locale: string) => void; @@ -26,11 +26,11 @@ export interface ActiveFormatsRuntime { dispose: () => void; } -export function createActiveFormatsLocaleSource( - options: ActiveFormatsLocaleSourceOptions -): ActiveFormatsLocaleSource { +export function createActiveFormatLocaleSource( + options: ActiveFormatLocaleSourceOptions +): ActiveFormatLocaleSource { let currentLocale = options.localeSource?.getLocale() ?? options.locale; - const source: FormatsLocaleSource = { + const source: FormatLocaleSource = { getLocale: () => currentLocale ?? options.localeSource?.getLocale() ?? options.locale ?? '', onLocaleChange: (fn) => { const unsubscribe = options.localeSource?.onLocaleChange?.((locale) => { @@ -50,9 +50,9 @@ export function createActiveFormatsLocaleSource( }; } -export function createActiveFormatsRuntime( - options: ActiveFormatsRuntimeOptions -): ActiveFormatsRuntime { +export function createActiveFormatRuntime( + options: ActiveFormatRuntimeOptions +): ActiveFormatRuntime { let version = $state(0); const changeListeners = new Set<() => void>(); const localeListeners = new Set<(locale: string) => void>(); @@ -98,7 +98,7 @@ export function createActiveFormatsRuntime( } export function readFrom( - runtime: Pick, + runtime: Pick, fn: (...args: TArgs) => TResult ): (...args: TArgs) => TResult { return (...args) => { @@ -108,7 +108,7 @@ export function readFrom( } export function writeTo( - runtime: Pick, + runtime: Pick, fn: (...args: TArgs) => TResult ): (...args: TArgs) => TResult { return (...args) => { diff --git a/src/arts/formats/auto-state.ts b/src/arts/format/auto-state.ts similarity index 100% rename from src/arts/formats/auto-state.ts rename to src/arts/format/auto-state.ts diff --git a/src/arts/format/consts.ts b/src/arts/format/consts.ts new file mode 100644 index 0000000..60f147c --- /dev/null +++ b/src/arts/format/consts.ts @@ -0,0 +1,3 @@ +export const FORMAT_MODULE = 'format'; +export const FORMAT_DEFAULT_LOCALE = 'en-US'; +export const FORMAT_AUTO_VALUE = 'auto'; diff --git a/src/arts/formats/currency/README.md b/src/arts/format/currency/README.md similarity index 100% rename from src/arts/formats/currency/README.md rename to src/arts/format/currency/README.md diff --git a/src/arts/formats/currency/active-currency.svelte.ts b/src/arts/format/currency/active-currency.svelte.ts similarity index 94% rename from src/arts/formats/currency/active-currency.svelte.ts rename to src/arts/format/currency/active-currency.svelte.ts index d614f69..627cd5e 100644 --- a/src/arts/formats/currency/active-currency.svelte.ts +++ b/src/arts/format/currency/active-currency.svelte.ts @@ -1,4 +1,4 @@ -import { createActiveFormatsRuntime, readFrom } from '../active-runtime.svelte'; +import { createActiveFormatRuntime, readFrom } from '../active-runtime.svelte'; import { createEngineCurrency } from './engine-currency'; import type { ActiveCurrency, @@ -28,7 +28,7 @@ export function createActiveCurrency(options: ActiveCurrencyOptions = {}): Activ if (before !== after) notifyCurrency(after); } - const runtime = createActiveFormatsRuntime({ + const runtime = createActiveFormatRuntime({ localeSource, getLocale: () => engine.getLocale(), setLocale(locale) { diff --git a/src/arts/formats/currency/consts.ts b/src/arts/format/currency/consts.ts similarity index 57% rename from src/arts/formats/currency/consts.ts rename to src/arts/format/currency/consts.ts index e179d39..a78cbdd 100644 --- a/src/arts/formats/currency/consts.ts +++ b/src/arts/format/currency/consts.ts @@ -1,10 +1,10 @@ -export const FORMATS_CURR_MODULE = 'formats.curr'; +export const FORMAT_CURR_MODULE = 'format.curr'; export const CURRENCY_DIAGNOSTIC_EVENTS = { - RATES_PROVIDER_MISSING: 'formats.curr.rates_provider_missing', - RATE_NOT_AVAILABLE: 'formats.curr.rate_not_available', - INVALID_RATE: 'formats.curr.invalid_rate', - RATE_FETCH_FAILED: 'formats.curr.rate_fetch_failed' + RATES_PROVIDER_MISSING: 'format.curr.rates_provider_missing', + RATE_NOT_AVAILABLE: 'format.curr.rate_not_available', + INVALID_RATE: 'format.curr.invalid_rate', + RATE_FETCH_FAILED: 'format.curr.rate_fetch_failed' } as const; export const AUTO_CURRENCY = 'auto'; diff --git a/src/arts/formats/currency/diagnostics.ts b/src/arts/format/currency/diagnostics.ts similarity index 92% rename from src/arts/formats/currency/diagnostics.ts rename to src/arts/format/currency/diagnostics.ts index cc456e7..54a364e 100644 --- a/src/arts/formats/currency/diagnostics.ts +++ b/src/arts/format/currency/diagnostics.ts @@ -6,7 +6,7 @@ import { type Diagnostics, type Logger } from '$libs/logger'; -import { CURRENCY_DIAGNOSTIC_EVENTS, FORMATS_CURR_MODULE } from './consts'; +import { CURRENCY_DIAGNOSTIC_EVENTS, FORMAT_CURR_MODULE } from './consts'; import { CURRENCY_ERROR_MESSAGES } from './errors'; import type { CurrencyCode } from './types'; @@ -52,7 +52,7 @@ const CURRENCY_DIAGNOSTIC_LOGS: DiagnosticCatalog = { export function createCurrencyDiagnostics(logger?: Logger): CurrencyDiagnostics { return createCatalogDiagnostics({ logger, - defaultCategory: FORMATS_CURR_MODULE, + defaultCategory: FORMAT_CURR_MODULE, catalog: CURRENCY_DIAGNOSTIC_LOGS }); } @@ -63,7 +63,7 @@ export function emitCurrencyDiagnostic( meta: CurrencyDiagnosticMeta ): void { diagnostics.emit({ - artifact: FORMATS_CURR_MODULE, + artifact: FORMAT_CURR_MODULE, type, meta }); diff --git a/src/arts/formats/currency/engine-currency.ts b/src/arts/format/currency/engine-currency.ts similarity index 96% rename from src/arts/formats/currency/engine-currency.ts rename to src/arts/format/currency/engine-currency.ts index fdaf9a3..53b2a00 100644 --- a/src/arts/formats/currency/engine-currency.ts +++ b/src/arts/format/currency/engine-currency.ts @@ -6,7 +6,7 @@ import { DEFAULT_CURRENCY_SIGN, } from './consts'; import { createAutoState } from '../auto-state'; -import { createFormatsLocaleState } from '../locale-state'; +import { createFormatLocaleState } from '../locale-state'; import { createCurrencyDiagnostics, emitCurrencyDiagnostic } from './diagnostics'; import { normalizeCurrencyCode, normalizeCurrencyMode } from './helpers'; import { resolveCurrency } from './locale-defaults'; @@ -49,7 +49,7 @@ function getCurrencyFormatter( } export function createEngineCurrency(options: EngineCurrencyOptions = {}): EngineCurrency { - const { getLocale, setLocale } = createFormatsLocaleState(options.locale); + const { getLocale, setLocale } = createFormatLocaleState(options.locale); const currencyState = createAutoState({ initial: options.currency, auto: AUTO_CURRENCY, diff --git a/src/arts/formats/currency/errors.ts b/src/arts/format/currency/errors.ts similarity index 100% rename from src/arts/formats/currency/errors.ts rename to src/arts/format/currency/errors.ts diff --git a/src/arts/formats/currency/helpers.ts b/src/arts/format/currency/helpers.ts similarity index 100% rename from src/arts/formats/currency/helpers.ts rename to src/arts/format/currency/helpers.ts diff --git a/src/arts/formats/currency/index.ts b/src/arts/format/currency/index.ts similarity index 98% rename from src/arts/formats/currency/index.ts rename to src/arts/format/currency/index.ts index 4ef27d2..2987a9f 100644 --- a/src/arts/formats/currency/index.ts +++ b/src/arts/format/currency/index.ts @@ -3,7 +3,7 @@ // or locale currency tables they never reach. export { - FORMATS_CURR_MODULE, + FORMAT_CURR_MODULE, AUTO_CURRENCY, CURRENCY_DIAGNOSTIC_EVENTS, DEFAULT_CURRENCY, diff --git a/src/arts/formats/currency/locale-currencies.ts b/src/arts/format/currency/locale-currencies.ts similarity index 100% rename from src/arts/formats/currency/locale-currencies.ts rename to src/arts/format/currency/locale-currencies.ts diff --git a/src/arts/formats/currency/locale-defaults.ts b/src/arts/format/currency/locale-defaults.ts similarity index 100% rename from src/arts/formats/currency/locale-defaults.ts rename to src/arts/format/currency/locale-defaults.ts diff --git a/src/arts/formats/currency/rates.ts b/src/arts/format/currency/rates.ts similarity index 100% rename from src/arts/formats/currency/rates.ts rename to src/arts/format/currency/rates.ts diff --git a/src/arts/formats/currency/test/active-currency.test.ts b/src/arts/format/currency/test/active-currency.test.ts similarity index 100% rename from src/arts/formats/currency/test/active-currency.test.ts rename to src/arts/format/currency/test/active-currency.test.ts diff --git a/src/arts/formats/currency/test/barrel.test.ts b/src/arts/format/currency/test/barrel.test.ts similarity index 89% rename from src/arts/formats/currency/test/barrel.test.ts rename to src/arts/format/currency/test/barrel.test.ts index 20a236f..3eea445 100644 --- a/src/arts/formats/currency/test/barrel.test.ts +++ b/src/arts/format/currency/test/barrel.test.ts @@ -3,7 +3,7 @@ import { AUTO_CURRENCY, CURRENCY_ERROR_MESSAGES, DEFAULT_CURRENCY, - FORMATS_CURR_MODULE, + FORMAT_CURR_MODULE, createEngineCurrency, createRates, resolveCurrency @@ -14,7 +14,7 @@ describe('curr public barrel', () => { expect(createEngineCurrency).toBeTypeOf('function'); expect(createRates).toBeTypeOf('function'); expect(resolveCurrency).toBeTypeOf('function'); - expect(FORMATS_CURR_MODULE).toBe('formats.curr'); + expect(FORMAT_CURR_MODULE).toBe('format.curr'); expect(AUTO_CURRENCY).toBe('auto'); expect(DEFAULT_CURRENCY).toBe('USD'); expect(CURRENCY_ERROR_MESSAGES.RATES_PROVIDER_MISSING).toContain('[fmts.curr]'); diff --git a/src/arts/formats/currency/test/engine-currency.test.ts b/src/arts/format/currency/test/engine-currency.test.ts similarity index 96% rename from src/arts/formats/currency/test/engine-currency.test.ts rename to src/arts/format/currency/test/engine-currency.test.ts index bfe8768..c66f81d 100644 --- a/src/arts/formats/currency/test/engine-currency.test.ts +++ b/src/arts/format/currency/test/engine-currency.test.ts @@ -3,7 +3,7 @@ import { createEngineLogger, LogLevel } from '$logger'; import { AUTO_CURRENCY, CURRENCY_ERROR_MESSAGES, - FORMATS_CURR_MODULE, + FORMAT_CURR_MODULE, createEngineCurrency, createRates, type EngineCurrency @@ -79,7 +79,7 @@ describe('createEngineCurrency()', () => { await expect(curr.convert(10, 'USD')).resolves.toBeUndefined(); - const logs = logger.getLogs({ category: FORMATS_CURR_MODULE, level: LogLevel.DEBUG }); + const logs = logger.getLogs({ category: FORMAT_CURR_MODULE, level: LogLevel.DEBUG }); expect(logs).toHaveLength(1); expect(logs[0].message).toBe(CURRENCY_ERROR_MESSAGES.RATES_PROVIDER_MISSING); }); diff --git a/src/arts/formats/currency/test/locale-defaults.test.ts b/src/arts/format/currency/test/locale-defaults.test.ts similarity index 100% rename from src/arts/formats/currency/test/locale-defaults.test.ts rename to src/arts/format/currency/test/locale-defaults.test.ts diff --git a/src/arts/formats/currency/test/rates.test.ts b/src/arts/format/currency/test/rates.test.ts similarity index 94% rename from src/arts/formats/currency/test/rates.test.ts rename to src/arts/format/currency/test/rates.test.ts index 59a82ab..7b04b33 100644 --- a/src/arts/formats/currency/test/rates.test.ts +++ b/src/arts/format/currency/test/rates.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest'; import { createEngineLogger, LogLevel } from '$logger'; -import { CURRENCY_ERROR_MESSAGES, FORMATS_CURR_MODULE, createRates } from '..'; +import { CURRENCY_ERROR_MESSAGES, FORMAT_CURR_MODULE, createRates } from '..'; describe('createRates()', () => { it('caches direct and inverse rates', async () => { @@ -73,7 +73,7 @@ describe('createRates()', () => { await expect(rates.getRate('EUR', 'USD')).resolves.toBeUndefined(); await expect(rates.getRate('USD', 'EUR')).resolves.toBeUndefined(); - expect(logger.getLogs({ category: FORMATS_CURR_MODULE, level: LogLevel.WARN })[0].message).toBe( + expect(logger.getLogs({ category: FORMAT_CURR_MODULE, level: LogLevel.WARN })[0].message).toBe( CURRENCY_ERROR_MESSAGES.INVALID_RATE('EUR', 'USD', 0) ); }); diff --git a/src/arts/formats/currency/types.ts b/src/arts/format/currency/types.ts similarity index 92% rename from src/arts/formats/currency/types.ts rename to src/arts/format/currency/types.ts index d3f0e00..07c3d81 100644 --- a/src/arts/formats/currency/types.ts +++ b/src/arts/format/currency/types.ts @@ -1,12 +1,12 @@ import type { Logger } from '$libs/logger'; import type { AUTO_CURRENCY } from './consts'; -import type { FormatsLocaleInput, FormatsLocaleSource } from '../types'; +import type { FormatLocaleInput, FormatLocaleSource } from '../types'; import type { EngineNumbers, NumbersCurrencyFormatOptions } from '../numbers'; export type CurrencyCode = string; export type CurrencyMode = typeof AUTO_CURRENCY | CurrencyCode; -export type CurrencyLocaleInput = FormatsLocaleInput; -export type CurrencyLocaleSource = FormatsLocaleSource; +export type CurrencyLocaleInput = FormatLocaleInput; +export type CurrencyLocaleSource = FormatLocaleSource; export interface CurrencyFormatOptions extends NumbersCurrencyFormatOptions {} diff --git a/src/arts/formats/dates/active-dates.svelte.ts b/src/arts/format/dates/active-dates.svelte.ts similarity index 90% rename from src/arts/formats/dates/active-dates.svelte.ts rename to src/arts/format/dates/active-dates.svelte.ts index 18ac990..796a9fa 100644 --- a/src/arts/formats/dates/active-dates.svelte.ts +++ b/src/arts/format/dates/active-dates.svelte.ts @@ -1,4 +1,4 @@ -import { createActiveFormatsRuntime, readFrom, writeTo } from '../active-runtime.svelte'; +import { createActiveFormatRuntime, readFrom, writeTo } from '../active-runtime.svelte'; import { createEngineDates } from './engine-dates'; import type { ActiveDates, ActiveDatesOptions } from './types'; @@ -9,7 +9,7 @@ export function createActiveDates(options: ActiveDatesOptions = {}): ActiveDates locale: localeSource?.getLocale() ?? options.locale }); - const runtime = createActiveFormatsRuntime({ + const runtime = createActiveFormatRuntime({ localeSource, getLocale: () => engine.getLocale(), setLocale: (locale) => engine.setLocale(locale) diff --git a/src/arts/format/dates/consts.ts b/src/arts/format/dates/consts.ts new file mode 100644 index 0000000..da2167b --- /dev/null +++ b/src/arts/format/dates/consts.ts @@ -0,0 +1 @@ +export const FORMAT_DATES_MODULE = 'format.dates'; diff --git a/src/arts/formats/dates/engine-dates.ts b/src/arts/format/dates/engine-dates.ts similarity index 93% rename from src/arts/formats/dates/engine-dates.ts rename to src/arts/format/dates/engine-dates.ts index 0ee8ddf..14dc51a 100644 --- a/src/arts/formats/dates/engine-dates.ts +++ b/src/arts/format/dates/engine-dates.ts @@ -1,6 +1,6 @@ import { getCachedDateFormat, resolveDateOrder, resolveHourCycle } from '$libs/days'; import { createAutoState } from '../auto-state'; -import { createFormatsLocaleState } from '../locale-state'; +import { createFormatLocaleState } from '../locale-state'; import type { DateOrder, DateOrderMode, @@ -23,7 +23,7 @@ function withHourCycle( } export function createEngineDates(options: EngineDatesOptions = {}): EngineDates { - const { getLocale, setLocale } = createFormatsLocaleState(options.locale); + const { getLocale, setLocale } = createFormatLocaleState(options.locale); const dateOrder = createAutoState({ initial: options.dateOrder, auto: 'auto' diff --git a/src/arts/formats/dates/index.ts b/src/arts/format/dates/index.ts similarity index 85% rename from src/arts/formats/dates/index.ts rename to src/arts/format/dates/index.ts index 75947a1..53dd292 100644 --- a/src/arts/formats/dates/index.ts +++ b/src/arts/format/dates/index.ts @@ -1,4 +1,4 @@ -export { FORMATS_DATES_MODULE } from './consts'; +export { FORMAT_DATES_MODULE } from './consts'; export { createActiveDates } from './active-dates.svelte'; export { createEngineDates } from './engine-dates'; export type { diff --git a/src/arts/formats/dates/test/active-dates.test.ts b/src/arts/format/dates/test/active-dates.test.ts similarity index 100% rename from src/arts/formats/dates/test/active-dates.test.ts rename to src/arts/format/dates/test/active-dates.test.ts diff --git a/src/arts/formats/dates/test/engine-dates.test.ts b/src/arts/format/dates/test/engine-dates.test.ts similarity index 100% rename from src/arts/formats/dates/test/engine-dates.test.ts rename to src/arts/format/dates/test/engine-dates.test.ts diff --git a/src/arts/formats/dates/types.ts b/src/arts/format/dates/types.ts similarity index 90% rename from src/arts/formats/dates/types.ts rename to src/arts/format/dates/types.ts index 566c3e4..1e5a2bb 100644 --- a/src/arts/formats/dates/types.ts +++ b/src/arts/format/dates/types.ts @@ -1,5 +1,5 @@ import type { DateOrder, HourCycle } from '$libs/days'; -import type { FormatsLocaleInput, FormatsLocaleSource } from '../types'; +import type { FormatLocaleInput, FormatLocaleSource } from '../types'; export type { DateOrder, HourCycle }; @@ -9,7 +9,7 @@ export type HourCycleMode = 'auto' | HourCycle; export interface DatesFormatOptions extends Intl.DateTimeFormatOptions {} export interface EngineDatesOptions { - locale?: FormatsLocaleInput; + locale?: FormatLocaleInput; dateOrder?: DateOrderMode; hourCycle?: HourCycleMode; } @@ -32,7 +32,7 @@ export interface EngineDates { export interface ActiveDatesOptions extends Omit { locale?: string; - localeSource?: FormatsLocaleSource; + localeSource?: FormatLocaleSource; } export interface ActiveDates extends EngineDates { diff --git a/src/arts/formats/engine-formats.ts b/src/arts/format/engine-formats.ts similarity index 86% rename from src/arts/formats/engine-formats.ts rename to src/arts/format/engine-formats.ts index 63d5f3a..347588e 100644 --- a/src/arts/formats/engine-formats.ts +++ b/src/arts/format/engine-formats.ts @@ -2,22 +2,22 @@ import { createEngineCurrency } from './currency'; import { createEngineDates } from './dates'; import { createEngineNumbers } from './numbers'; import { createEngineUnits } from './units'; -import { createFormatsLocaleState } from './locale-state'; +import { createFormatLocaleState } from './locale-state'; import type { EngineCurrency, EngineCurrencyOptions } from './currency'; import type { EngineDates, EngineDatesOptions } from './dates'; import type { EngineNumbers, EngineNumbersOptions } from './numbers'; import type { EngineUnits, EngineUnitsOptions } from './units'; -import type { FormatsLocaleInput } from './types'; +import type { FormatLocaleInput } from './types'; -export interface EngineFormatsOptions { - locale?: FormatsLocaleInput; +export interface EngineFormatOptions { + locale?: FormatLocaleInput; numbers?: Omit; currency?: Omit; units?: Omit; dates?: Omit; } -export interface EngineFormats { +export interface EngineFormat { readonly numbers: EngineNumbers; readonly currency: EngineCurrency; readonly units: EngineUnits; @@ -31,8 +31,8 @@ export interface EngineFormats { dispose: () => void; } -export function createEngineFormats(options: EngineFormatsOptions = {}): EngineFormats { - const localeState = createFormatsLocaleState(options.locale); +export function createEngineFormat(options: EngineFormatOptions = {}): EngineFormat { + const localeState = createFormatLocaleState(options.locale); const locale = localeState.getLocale; const numbers = createEngineNumbers({ ...options.numbers, locale }); diff --git a/src/arts/format/errors.ts b/src/arts/format/errors.ts new file mode 100644 index 0000000..98346d3 --- /dev/null +++ b/src/arts/format/errors.ts @@ -0,0 +1,5 @@ +import { FORMAT_MODULE } from './consts.ts'; + +export const FORMAT_ERROR_MESSAGES = { + INVALID_LOCALE: (locale: string): string => `[${FORMAT_MODULE}] Invalid locale: "${locale}".` +} as const; diff --git a/src/arts/formats/helpers.ts b/src/arts/format/helpers.ts similarity index 63% rename from src/arts/formats/helpers.ts rename to src/arts/format/helpers.ts index 6c84172..741a4ea 100644 --- a/src/arts/formats/helpers.ts +++ b/src/arts/format/helpers.ts @@ -1,5 +1,5 @@ -import { FORMATS_DEFAULT_LOCALE } from './consts'; -import type { FormatsLocaleInput } from './types'; +import { FORMAT_DEFAULT_LOCALE } from './consts'; +import type { FormatLocaleInput } from './types'; const REGION_CODE_PATTERN = /^[A-Z]{2}$|^\d{3}$/; @@ -19,12 +19,12 @@ export function getLocaleRegion(locale: string): string | undefined { return undefined; } -export function resolveLocaleInput(input?: FormatsLocaleInput): { +export function resolveLocaleInput(input?: FormatLocaleInput): { getter?: () => string; value: string; } { if (typeof input === 'function') { - return { getter: input, value: FORMATS_DEFAULT_LOCALE }; + return { getter: input, value: FORMAT_DEFAULT_LOCALE }; } - return { value: normalizeLocaleTag(input ?? FORMATS_DEFAULT_LOCALE) || FORMATS_DEFAULT_LOCALE }; + return { value: normalizeLocaleTag(input ?? FORMAT_DEFAULT_LOCALE) || FORMAT_DEFAULT_LOCALE }; } diff --git a/src/arts/formats/index.ts b/src/arts/format/index.ts similarity index 61% rename from src/arts/formats/index.ts rename to src/arts/format/index.ts index 8298d1b..96dfa64 100644 --- a/src/arts/formats/index.ts +++ b/src/arts/format/index.ts @@ -1,18 +1,18 @@ // fmts public surface. Named re-exports so the bundler can drop sub-formats // the consumer never reaches. The four sub-artifacts (`currency`, `dates`, // `numbers`, `units`) are also exposed as namespaces for the -// `App.Formats.currency.format(...)` style; with `sideEffects: false` in +// `App.Format.currency.format(...)` style; with `sideEffects: false` in // package.json the namespace pattern still tree-shakes member access. -export { FORMATS_MODULE, FORMATS_DEFAULT_LOCALE, FORMATS_AUTO_VALUE } from './consts'; -export { createEngineFormats } from './engine-formats'; -export { createActiveFormats } from './active-formats.svelte'; -export { FORMATS_ERROR_MESSAGES } from './errors'; +export { FORMAT_MODULE, FORMAT_DEFAULT_LOCALE, FORMAT_AUTO_VALUE } from './consts'; +export { createEngineFormat } from './engine-formats'; +export { createActiveFormat } from './active-formats.svelte'; +export { FORMAT_ERROR_MESSAGES } from './errors'; export { normalizeLocaleTag, getLocaleRegion, resolveLocaleInput } from './helpers'; -export type { EngineFormatsOptions, EngineFormats } from './engine-formats'; -export type { ActiveFormatsOptions, ActiveFormats } from './active-formats.svelte'; -export type { FormatsLocaleInput, FormatsLocaleSource, FormatsDisposable } from './types'; +export type { EngineFormatOptions, EngineFormat } from './engine-formats'; +export type { ActiveFormatOptions, ActiveFormat } from './active-formats.svelte'; +export type { FormatLocaleInput, FormatLocaleSource, FormatDisposable } from './types'; // Per-domain factories — direct named imports so consumers can pull only // what they use. @@ -21,7 +21,7 @@ export { createActiveDates, createEngineDates } from './dates'; export { createActiveNumbers, createEngineNumbers } from './numbers'; export { createActiveUnits, createEngineUnits } from './units'; -// Per-domain namespaces — for `import { currency } from '$formats'` ergonomics. +// Per-domain namespaces — for `import { currency } from '$format'` ergonomics. export * as currency from './currency'; export * as dates from './dates'; export * as numbers from './numbers'; diff --git a/src/arts/formats/locale-state.ts b/src/arts/format/locale-state.ts similarity index 55% rename from src/arts/formats/locale-state.ts rename to src/arts/format/locale-state.ts index c17dcd8..be8b5b7 100644 --- a/src/arts/formats/locale-state.ts +++ b/src/arts/format/locale-state.ts @@ -1,13 +1,13 @@ -import { FORMATS_DEFAULT_LOCALE } from './consts'; +import { FORMAT_DEFAULT_LOCALE } from './consts'; import { normalizeLocaleTag, resolveLocaleInput } from './helpers'; -import type { FormatsLocaleInput } from './types'; +import type { FormatLocaleInput } from './types'; -export interface FormatsLocaleState { +export interface FormatLocaleState { getLocale: () => string; setLocale: (locale: string) => void; } -export function createFormatsLocaleState(input?: FormatsLocaleInput): FormatsLocaleState { +export function createFormatLocaleState(input?: FormatLocaleInput): FormatLocaleState { const localeInput = resolveLocaleInput(input); let localeGetter = localeInput.getter; let currentLocale = localeInput.value; @@ -15,12 +15,12 @@ export function createFormatsLocaleState(input?: FormatsLocaleInput): FormatsLoc return { getLocale() { const locale = normalizeLocaleTag(localeGetter?.() ?? currentLocale); - return locale || FORMATS_DEFAULT_LOCALE; + return locale || FORMAT_DEFAULT_LOCALE; }, setLocale(locale) { localeGetter = undefined; - currentLocale = normalizeLocaleTag(locale) || FORMATS_DEFAULT_LOCALE; + currentLocale = normalizeLocaleTag(locale) || FORMAT_DEFAULT_LOCALE; } }; } diff --git a/src/arts/formats/numbers/active-numbers.svelte.ts b/src/arts/format/numbers/active-numbers.svelte.ts similarity index 93% rename from src/arts/formats/numbers/active-numbers.svelte.ts rename to src/arts/format/numbers/active-numbers.svelte.ts index af3af1f..608c9ee 100644 --- a/src/arts/formats/numbers/active-numbers.svelte.ts +++ b/src/arts/format/numbers/active-numbers.svelte.ts @@ -1,4 +1,4 @@ -import { createActiveFormatsRuntime, readFrom, writeTo } from '../active-runtime.svelte'; +import { createActiveFormatRuntime, readFrom, writeTo } from '../active-runtime.svelte'; import { createEngineNumbers } from './engine-numbers'; import type { ActiveNumbers, ActiveNumbersOptions } from './types'; @@ -9,7 +9,7 @@ export function createActiveNumbers(options: ActiveNumbersOptions = {}): ActiveN locale: localeSource?.getLocale() ?? options.locale }); - const runtime = createActiveFormatsRuntime({ + const runtime = createActiveFormatRuntime({ localeSource, getLocale: () => engine.getLocale(), setLocale: (locale) => engine.setLocale(locale) diff --git a/src/arts/format/numbers/consts.ts b/src/arts/format/numbers/consts.ts new file mode 100644 index 0000000..48133ef --- /dev/null +++ b/src/arts/format/numbers/consts.ts @@ -0,0 +1 @@ +export const FORMAT_NUMS_MODULE = 'format.nums'; diff --git a/src/arts/formats/numbers/engine-numbers.ts b/src/arts/format/numbers/engine-numbers.ts similarity index 92% rename from src/arts/formats/numbers/engine-numbers.ts rename to src/arts/format/numbers/engine-numbers.ts index 1351645..98b03c5 100644 --- a/src/arts/formats/numbers/engine-numbers.ts +++ b/src/arts/format/numbers/engine-numbers.ts @@ -1,6 +1,6 @@ -import { FORMATS_AUTO_VALUE } from '../consts'; +import { FORMAT_AUTO_VALUE } from '../consts'; import { createAutoState } from '../auto-state'; -import { createFormatsLocaleState } from '../locale-state'; +import { createFormatLocaleState } from '../locale-state'; import { getCachedNumberFormat, getNumberFormatPart, @@ -18,18 +18,18 @@ import type { } from './types'; export function createEngineNumbers(options: EngineNumbersOptions = {}): EngineNumbers { - const { getLocale, setLocale } = createFormatsLocaleState(options.locale); + const { getLocale, setLocale } = createFormatLocaleState(options.locale); const decimalSeparator = createAutoState({ initial: options.decimalSeparator, - auto: FORMATS_AUTO_VALUE + auto: FORMAT_AUTO_VALUE }); const groupSeparator = createAutoState({ initial: options.groupSeparator, - auto: FORMATS_AUTO_VALUE + auto: FORMAT_AUTO_VALUE }); const grouping = createAutoState({ initial: options.grouping, - auto: FORMATS_AUTO_VALUE + auto: FORMAT_AUTO_VALUE }); const defaultFormat = options.format ?? {}; diff --git a/src/arts/formats/numbers/index.ts b/src/arts/format/numbers/index.ts similarity index 88% rename from src/arts/formats/numbers/index.ts rename to src/arts/format/numbers/index.ts index b9f5324..a8587d8 100644 --- a/src/arts/formats/numbers/index.ts +++ b/src/arts/format/numbers/index.ts @@ -1,4 +1,4 @@ -export { FORMATS_NUMS_MODULE } from './consts'; +export { FORMAT_NUMS_MODULE } from './consts'; export { createActiveNumbers } from './active-numbers.svelte'; export { createEngineNumbers } from './engine-numbers'; export type { diff --git a/src/arts/formats/numbers/number-format.ts b/src/arts/format/numbers/number-format.ts similarity index 100% rename from src/arts/formats/numbers/number-format.ts rename to src/arts/format/numbers/number-format.ts diff --git a/src/arts/formats/numbers/number-parse.ts b/src/arts/format/numbers/number-parse.ts similarity index 100% rename from src/arts/formats/numbers/number-parse.ts rename to src/arts/format/numbers/number-parse.ts diff --git a/src/arts/formats/numbers/test/active-numbers.test.ts b/src/arts/format/numbers/test/active-numbers.test.ts similarity index 100% rename from src/arts/formats/numbers/test/active-numbers.test.ts rename to src/arts/format/numbers/test/active-numbers.test.ts diff --git a/src/arts/formats/numbers/test/engine-numbers.test.ts b/src/arts/format/numbers/test/engine-numbers.test.ts similarity index 100% rename from src/arts/formats/numbers/test/engine-numbers.test.ts rename to src/arts/format/numbers/test/engine-numbers.test.ts diff --git a/src/arts/formats/numbers/types.ts b/src/arts/format/numbers/types.ts similarity index 89% rename from src/arts/formats/numbers/types.ts rename to src/arts/format/numbers/types.ts index a0b0ea8..0c053d8 100644 --- a/src/arts/formats/numbers/types.ts +++ b/src/arts/format/numbers/types.ts @@ -1,7 +1,7 @@ -import type { FORMATS_AUTO_VALUE } from '../consts'; -import type { FormatsLocaleInput, FormatsLocaleSource } from '../types'; +import type { FORMAT_AUTO_VALUE } from '../consts'; +import type { FormatLocaleInput, FormatLocaleSource } from '../types'; -export type NumbersAuto = typeof FORMATS_AUTO_VALUE; +export type NumbersAuto = typeof FORMAT_AUTO_VALUE; export type NumbersSeparatorMode = NumbersAuto | string; export type NumbersGroupingMode = NumbersAuto | boolean; @@ -23,7 +23,7 @@ export interface NumbersUnitFormatOptions extends Omit { locale?: string; - localeSource?: FormatsLocaleSource; + localeSource?: FormatLocaleSource; } export interface ActiveNumbers extends EngineNumbers { diff --git a/src/arts/formats/test/auto-state.test.ts b/src/arts/format/test/auto-state.test.ts similarity index 100% rename from src/arts/formats/test/auto-state.test.ts rename to src/arts/format/test/auto-state.test.ts diff --git a/src/arts/formats/test/formats.test.ts b/src/arts/format/test/formats.test.ts similarity index 84% rename from src/arts/formats/test/formats.test.ts rename to src/arts/format/test/formats.test.ts index 4b3aee6..bfe9bf9 100644 --- a/src/arts/formats/test/formats.test.ts +++ b/src/arts/format/test/formats.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it } from 'vitest'; -import { createActiveFormats, createEngineFormats } from '..'; +import { createActiveFormat, createEngineFormat } from '..'; -describe('createEngineFormats()', () => { +describe('createEngineFormat()', () => { it('composes number, currency, units and dates format engines', async () => { - const formats = createEngineFormats({ + const formats = createEngineFormat({ locale: 'en-US', currency: { rates: undefined @@ -27,9 +27,9 @@ describe('createEngineFormats()', () => { }); }); -describe('createActiveFormats()', () => { +describe('createActiveFormat()', () => { it('fans out locale changes to every active format module', () => { - const formats = createActiveFormats({ locale: 'en-US' }); + const formats = createActiveFormat({ locale: 'en-US' }); expect(formats.getLocale()).toBe('en-US'); expect(formats.currency.getCurrency()).toBe('USD'); diff --git a/src/arts/formats/test/locale-state.test.ts b/src/arts/format/test/locale-state.test.ts similarity index 60% rename from src/arts/formats/test/locale-state.test.ts rename to src/arts/format/test/locale-state.test.ts index 5d9dcc9..d323799 100644 --- a/src/arts/formats/test/locale-state.test.ts +++ b/src/arts/format/test/locale-state.test.ts @@ -1,20 +1,20 @@ import { describe, expect, it } from 'vitest'; -import { FORMATS_DEFAULT_LOCALE } from '../consts'; -import { createFormatsLocaleState } from '../locale-state'; +import { FORMAT_DEFAULT_LOCALE } from '../consts'; +import { createFormatLocaleState } from '../locale-state'; -describe('createFormatsLocaleState()', () => { +describe('createFormatLocaleState()', () => { it('normalizes locale strings and falls back to the default locale', () => { - const state = createFormatsLocaleState('es_ES'); + const state = createFormatLocaleState('es_ES'); expect(state.getLocale()).toBe('es-ES'); state.setLocale(''); - expect(state.getLocale()).toBe(FORMATS_DEFAULT_LOCALE); + expect(state.getLocale()).toBe(FORMAT_DEFAULT_LOCALE); }); it('tracks locale functions until a manual locale is set', () => { let locale = 'en_GB'; - const state = createFormatsLocaleState(() => locale); + const state = createFormatLocaleState(() => locale); expect(state.getLocale()).toBe('en-GB'); diff --git a/src/arts/formats/types.ts b/src/arts/format/types.ts similarity index 51% rename from src/arts/formats/types.ts rename to src/arts/format/types.ts index 8fd0ec6..a21791a 100644 --- a/src/arts/formats/types.ts +++ b/src/arts/format/types.ts @@ -1,14 +1,14 @@ import type { LocaleSource } from '$locale'; -export type FormatsLocaleInput = string | (() => string); +export type FormatLocaleInput = string | (() => string); /** - * Reactive locale provider consumed by `ActiveFormats`. Re-exported as a + * Reactive locale provider consumed by `ActiveFormat`. Re-exported as a * shared alias of `LocaleSource` so consumers can keep importing the local * name without breakage; new code should prefer `LocaleSource` directly. */ -export type FormatsLocaleSource = LocaleSource; +export type FormatLocaleSource = LocaleSource; -export interface FormatsDisposable { +export interface FormatDisposable { dispose: () => void; } diff --git a/src/arts/formats/units/active-units.svelte.ts b/src/arts/format/units/active-units.svelte.ts similarity index 89% rename from src/arts/formats/units/active-units.svelte.ts rename to src/arts/format/units/active-units.svelte.ts index 5743174..226f69b 100644 --- a/src/arts/formats/units/active-units.svelte.ts +++ b/src/arts/format/units/active-units.svelte.ts @@ -1,4 +1,4 @@ -import { createActiveFormatsRuntime, readFrom, writeTo } from '../active-runtime.svelte'; +import { createActiveFormatRuntime, readFrom, writeTo } from '../active-runtime.svelte'; import { createEngineUnits } from './engine-units'; import type { ActiveUnits, ActiveUnitsOptions } from './types'; @@ -9,7 +9,7 @@ export function createActiveUnits(options: ActiveUnitsOptions = {}): ActiveUnits locale: localeSource?.getLocale() ?? options.locale }); - const runtime = createActiveFormatsRuntime({ + const runtime = createActiveFormatRuntime({ localeSource, getLocale: () => engine.getLocale(), setLocale: (locale) => engine.setLocale(locale) diff --git a/src/arts/format/units/consts.ts b/src/arts/format/units/consts.ts new file mode 100644 index 0000000..585437c --- /dev/null +++ b/src/arts/format/units/consts.ts @@ -0,0 +1,3 @@ +export const FORMAT_UNTS_MODULE = 'format.unts'; + +export const AUTO_UNIT_SYSTEM = 'auto'; diff --git a/src/arts/formats/units/conversions.ts b/src/arts/format/units/conversions.ts similarity index 100% rename from src/arts/formats/units/conversions.ts rename to src/arts/format/units/conversions.ts diff --git a/src/arts/formats/units/defaults.ts b/src/arts/format/units/defaults.ts similarity index 100% rename from src/arts/formats/units/defaults.ts rename to src/arts/format/units/defaults.ts diff --git a/src/arts/formats/units/engine-units.ts b/src/arts/format/units/engine-units.ts similarity index 93% rename from src/arts/formats/units/engine-units.ts rename to src/arts/format/units/engine-units.ts index 32e624b..37391d1 100644 --- a/src/arts/formats/units/engine-units.ts +++ b/src/arts/format/units/engine-units.ts @@ -5,7 +5,7 @@ import { UnitsUnknownUnitError } from './errors'; import { resolveUnitSystem } from './locale-defaults'; import { UNIT_DEFINITIONS } from './unit-definitions'; import { createAutoState } from '../auto-state'; -import { createFormatsLocaleState } from '../locale-state'; +import { createFormatLocaleState } from '../locale-state'; import type { EngineUnits, EngineUnitsOptions, @@ -16,7 +16,7 @@ import type { } from './types'; export function createEngineUnits(options: EngineUnitsOptions = {}): EngineUnits { - const { getLocale, setLocale } = createFormatsLocaleState(options.locale); + const { getLocale, setLocale } = createFormatLocaleState(options.locale); const unitSystem = createAutoState({ initial: options.system, auto: AUTO_UNIT_SYSTEM diff --git a/src/arts/formats/units/errors.ts b/src/arts/format/units/errors.ts similarity index 100% rename from src/arts/formats/units/errors.ts rename to src/arts/format/units/errors.ts diff --git a/src/arts/formats/units/index.ts b/src/arts/format/units/index.ts similarity index 91% rename from src/arts/formats/units/index.ts rename to src/arts/format/units/index.ts index 5004d19..ad77b51 100644 --- a/src/arts/formats/units/index.ts +++ b/src/arts/format/units/index.ts @@ -1,4 +1,4 @@ -export { FORMATS_UNTS_MODULE, AUTO_UNIT_SYSTEM } from './consts'; +export { FORMAT_UNTS_MODULE, AUTO_UNIT_SYSTEM } from './consts'; export { createActiveUnits } from './active-units.svelte'; export { createEngineUnits } from './engine-units'; export { convert } from './conversions'; diff --git a/src/arts/formats/units/locale-defaults.ts b/src/arts/format/units/locale-defaults.ts similarity index 100% rename from src/arts/formats/units/locale-defaults.ts rename to src/arts/format/units/locale-defaults.ts diff --git a/src/arts/formats/units/test/active-units.test.ts b/src/arts/format/units/test/active-units.test.ts similarity index 100% rename from src/arts/formats/units/test/active-units.test.ts rename to src/arts/format/units/test/active-units.test.ts diff --git a/src/arts/formats/units/test/engine-units.test.ts b/src/arts/format/units/test/engine-units.test.ts similarity index 100% rename from src/arts/formats/units/test/engine-units.test.ts rename to src/arts/format/units/test/engine-units.test.ts diff --git a/src/arts/formats/units/types.ts b/src/arts/format/units/types.ts similarity index 92% rename from src/arts/formats/units/types.ts rename to src/arts/format/units/types.ts index e40c86b..a64003c 100644 --- a/src/arts/formats/units/types.ts +++ b/src/arts/format/units/types.ts @@ -1,5 +1,5 @@ import type { AUTO_UNIT_SYSTEM } from './consts'; -import type { FormatsLocaleInput, FormatsLocaleSource } from '../types'; +import type { FormatLocaleInput, FormatLocaleSource } from '../types'; import type { EngineNumbers, NumbersUnitFormatOptions } from '../numbers'; export type UnitSystem = 'metric' | 'imperial'; @@ -16,7 +16,7 @@ export interface UnitDefinition { } export interface EngineUnitsOptions { - locale?: FormatsLocaleInput; + locale?: FormatLocaleInput; system?: UnitSystemMode; numbers?: Pick; } @@ -38,7 +38,7 @@ export interface EngineUnits { export interface ActiveUnitsOptions extends Omit { locale?: string; - localeSource?: FormatsLocaleSource; + localeSource?: FormatLocaleSource; } export interface ActiveUnits extends EngineUnits { diff --git a/src/arts/formats/units/unit-definitions.ts b/src/arts/format/units/unit-definitions.ts similarity index 100% rename from src/arts/formats/units/unit-definitions.ts rename to src/arts/format/units/unit-definitions.ts diff --git a/src/arts/formats/consts.ts b/src/arts/formats/consts.ts deleted file mode 100644 index 8c23195..0000000 --- a/src/arts/formats/consts.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const FORMATS_MODULE = 'formats'; -export const FORMATS_DEFAULT_LOCALE = 'en-US'; -export const FORMATS_AUTO_VALUE = 'auto'; diff --git a/src/arts/formats/dates/consts.ts b/src/arts/formats/dates/consts.ts deleted file mode 100644 index 604b361..0000000 --- a/src/arts/formats/dates/consts.ts +++ /dev/null @@ -1 +0,0 @@ -export const FORMATS_DATES_MODULE = 'formats.dates'; diff --git a/src/arts/formats/errors.ts b/src/arts/formats/errors.ts deleted file mode 100644 index ed219fe..0000000 --- a/src/arts/formats/errors.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { FORMATS_MODULE } from './consts.ts'; - -export const FORMATS_ERROR_MESSAGES = { - INVALID_LOCALE: (locale: string): string => `[${FORMATS_MODULE}] Invalid locale: "${locale}".` -} as const; diff --git a/src/arts/formats/numbers/consts.ts b/src/arts/formats/numbers/consts.ts deleted file mode 100644 index beab5ca..0000000 --- a/src/arts/formats/numbers/consts.ts +++ /dev/null @@ -1 +0,0 @@ -export const FORMATS_NUMS_MODULE = 'formats.nums'; diff --git a/src/arts/formats/units/consts.ts b/src/arts/formats/units/consts.ts deleted file mode 100644 index 3fd712a..0000000 --- a/src/arts/formats/units/consts.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const FORMATS_UNTS_MODULE = 'formats.unts'; - -export const AUTO_UNIT_SYSTEM = 'auto'; diff --git a/src/arts/frontend/README.md b/src/arts/frontend/README.md index b7aa0cf..3898c31 100644 --- a/src/arts/frontend/README.md +++ b/src/arts/frontend/README.md @@ -67,7 +67,7 @@ App.Frontend.setTheme('forest'); ``` La regla importante es que `Lang` sigue siendo la fuente unica de locale. Si -`App.setLocale(...)` cambia el idioma, `Formats` y `Frontend` reaccionan desde +`App.setLocale(...)` cambia el idioma, `Format` y `Frontend` reaccionan desde la misma fuente, sin duplicar estado. ## Contrato Auto / Manual diff --git a/src/arts/lang/README.md b/src/arts/lang/README.md index a06d936..bedb647 100644 --- a/src/arts/lang/README.md +++ b/src/arts/lang/README.md @@ -362,7 +362,7 @@ lang.ts('plain text'); // 'plain text' lang.ts({ es: 'Hola', en: 'Hello' }); // 'Hola' (first non-empty entry) ``` -The locale is held as `$state` so consumers (Formats, Frontend) that +The locale is held as `$state` so consumers (Format, Frontend) that subscribe via `onLocaleChange` still react to `setLocale`. In DEV the mono lang warns once per unresolved path via the supplied logger diff --git a/src/arts/lang/mono-lang.svelte.ts b/src/arts/lang/mono-lang.svelte.ts index aa01bb0..ba387cf 100644 --- a/src/arts/lang/mono-lang.svelte.ts +++ b/src/arts/lang/mono-lang.svelte.ts @@ -24,7 +24,7 @@ export { LANG_MONO_LANG_CATEGORY } from '$libs/lang'; * Default initial locale for `createActiveMonoLang()`. The mono lang does not * translate, so the value is mostly cosmetic — it determines what * `getLocale()` returns until `setLocale()` is called and what consumers - * (Formats, Frontend) see when they subscribe to `onLocaleChange`. + * (Format, Frontend) see when they subscribe to `onLocaleChange`. */ export const DEFAULT_MONO_LOCALE: SupportedLocale = 'en'; @@ -43,7 +43,7 @@ export const DEFAULT_MONO_LOCALE: SupportedLocale = 'en'; * - `ts('#?common.ok|Save')` → `'Save'`. * - `ts({ es: 'Hola', en: 'Hello' })` → first non-empty entry. * - * The locale is held as `$state` so `Formats` and `Frontend`, which subscribe + * The locale is held as `$state` so `Format` and `Frontend`, which subscribe * to `onLocaleChange`, still see updates from `setLocale`. * * In DEV the mono lang warns (deduplicated by path, once per path) when diff --git a/src/arts/permissions/Can.svelte b/src/arts/perm/Can.svelte similarity index 87% rename from src/arts/permissions/Can.svelte rename to src/arts/perm/Can.svelte index e16135d..bbf2f2b 100644 --- a/src/arts/permissions/Can.svelte +++ b/src/arts/perm/Can.svelte @@ -1,7 +1,7 @@ - Permissions ($permissions) — Active + Perms ($perm) — Active

- The server decides. ActivePermissions and <Can /> + The server decides. ActivePerms and <Can /> are UX helpers only. Every protected read, write, channel join or command must call the server engine.

@@ -574,7 +574,7 @@ for (const obligation of decision.obligations ?? []) {

Mental model

Authorization is a server decision with a client mirror. The policy language in - $libs/permissions defines what can be said, $svrs/permissions evaluates it against + $libs/perm defines what can be said, $svrs/perm evaluates it against trusted actor/resource/context data, and $arts/perm only mirrors decisions for UI responsiveness. If a route, action, WebSocket channel or job touches protected data, the server engine must decide again even if the button was hidden by @@ -594,31 +594,31 @@ for (const obligation of decision.obligations ?? []) { createEnginePermissions(options)servercreateEnginePerms(options)serverAuthoritative runtime with check, assert, explain, what, who and filter. createPermissionHttpHandlers(engine, resolveActor)servercreatePermHttpHandlers(engine, resolveActor)serverHTTP bridge for the active client: check, batch, what, explain and handle. createActivePermissions(options)clientcreateActivePerms(options)clientRemote reflector with cache, snapshots, loading/error state and can(). App.createActivePermissions(options?)clientApp.createActivePerms(options?)clientApp-wired singleton. App injects Http and Logger; the endpoint remains explicit. Can.svelteclient UIConditional rendering helper backed by Permissions.can(...).Conditional rendering helper backed by Perms.can(...). @@ -796,7 +796,7 @@ for (const obligation of decision.obligations ?? []) {

The repository includes a PostgreSQL reference migration at - src/svrs/permissions/sql/postgres.sql. It defines + src/svrs/perm/sql/postgres.sql. It defines permission_policies, permission_relations and permission_decision_audit. Treat it as the supported starting point for a real database model, then adapt naming, migrations and ORM mappings to the application. @@ -807,7 +807,7 @@ for (const obligation of decision.obligations ?? []) { The default and simplest pattern is to keep policies in TypeScript. If a product needs admin-managed or tenant-specific rules, store the produced PolicyIR as JSON, validate it when loading, version it, and then pass the resulting array to - createEnginePermissions(). Do not persist fluent builder calls or executable + createEnginePerms(). Do not persist fluent builder calls or executable functions.

@@ -943,26 +943,26 @@ for (const obligation of decision.obligations ?? []) {

Snapshots and cache

- Permissions.snapshot() returns the serializable client cache. Use + Perms.snapshot() returns the serializable client cache. Use hydrate(snapshot) for SSR or to restore a known decision set, and call invalidate(scope?) after login, logout, role changes, session refreshes or resource mutations.

; - global?: Record; + decisions?: Record; + global?: Record; expiresAt?: string; }`} />

Can component

- <Can /> renders children only when Permissions.can(...) + <Can /> renders children only when Perms.can(...) resolves to true. It reads the active client from Svelte context via - setPermissionsContext(). + setPermsContext().

@@ -1019,14 +1019,14 @@ for (const obligation of decision.obligations ?? []) {

Testing

- Unit-test policy behavior directly against createEnginePermissions(), then test - remote behavior through createPermissionHttpHandlers() and - createActivePermissions(). The interactive page is + Unit-test policy behavior directly against createEnginePerms(), then test + remote behavior through createPermHttpHandlers() and + createActivePerms(). The interactive page is /test/perm.

diff --git a/src/web/routes/active/docs/svrs/+page.svelte b/src/web/routes/active/docs/svrs/+page.svelte index 4438904..0732948 100644 --- a/src/web/routes/active/docs/svrs/+page.svelte +++ b/src/web/routes/active/docs/svrs/+page.svelte @@ -12,7 +12,7 @@ } from '$svrs'; const Auth = AuthServer.createEngineAuth(options); -const Permissions = PermServer.createEnginePermissions(options); +const Perms = PermServer.createEnginePerms(options); const Cache = CacheServer.createEngineCache(options);`; const authExample = `import { @@ -48,20 +48,20 @@ const Auth = createEngineAuth({ });`; const permExample = `import { - createEnginePermissions, - createPermissionHttpHandlers, + createEnginePerms, + createPermHttpHandlers, definePermSchema, definePolicies, allow, attr -} from '$svrs/permissions'; +} from '$svrs/perm'; const schema = definePermSchema({ actors: { user: { attributes: { role: 'string' } } }, resources: { invoice: { actions: ['read', 'pay'] } } }); -const Permissions = createEnginePermissions({ +const Perms = createEnginePerms({ schema, policies: definePolicies(schema, [ allow('invoice.read').when(attr('actor.role').eq('admin')) @@ -69,7 +69,7 @@ const Permissions = createEnginePermissions({ logger: App.Logger }); -const handlers = createPermissionHttpHandlers(Permissions, resolveActorFromRequest);`; +const handlers = createPermHttpHandlers(Perms, resolveActorFromRequest);`; const cachExample = `import { createEngineCache, @@ -98,7 +98,7 @@ export const handle = createSvelteKitAuthHandle(Auth, { // +server.ts or +page.server.ts export const POST = async ({ request }) => { - await Permissions.assert({ + await Perms.assert({ actor: await resolveActor(request), action: 'invoice.pay', resource: await loadInvoice(request), @@ -144,7 +144,7 @@ export const POST = async ({ request }) => { }, { step: 'Shared contracts', - where: 'src/libs/auth, src/libs/permissions, src/libs/cache', + where: 'src/libs/auth, src/libs/perm, src/libs/cache', rule: 'Put reusable types, constants and pure helpers in libs before duplicating them inside a server module.' }, { @@ -154,7 +154,7 @@ export const POST = async ({ request }) => { }, { step: 'Security boundaries', - where: '$svrs/auth, $svrs/permissions, $svrs/cache', + where: '$svrs/auth, $svrs/perm, $svrs/cache', rule: 'Auth proves identity, perm authorizes actions and cach preserves private scopes. Do not merge those responsibilities.' }, { @@ -164,7 +164,7 @@ export const POST = async ({ request }) => { }, { step: 'Tests', - where: 'src/svrs/auth/test, src/svrs/permissions/test, src/svrs/cache/test and /test/ecosystem', + where: 'src/svrs/auth/test, src/svrs/perm/test, src/svrs/cache/test and /test/ecosystem', rule: 'Any server behavior change needs contract tests and at least one integration scenario.' } ] as const; @@ -180,8 +180,8 @@ export const POST = async ({ request }) => { title="Server Modules" alias="$svrs" summary="Server-authoritative engines for auth, permissions and cache. This is where security decisions, request-scoped composition and backend adapters live." - factories={['createEngineAuth', 'createEnginePermissions', 'createEngineCache']} - dependsOn={['$libs/auth', '$libs/permissions', '$libs/cache', '$logger', '$timer', '$http']} + factories={['createEngineAuth', 'createEnginePerms', 'createEngineCache']} + dependsOn={['$libs/auth', '$libs/perm', '$libs/cache', '$logger', '$timer', '$http']} layer="Server engines" /> @@ -196,7 +196,7 @@ export const POST = async ({ request }) => {

Overview

The framework has three server modules today: $svrs/auth, - $svrs/permissions and $svrs/cache. They exist because these artifacts + $svrs/perm and $svrs/cache. They exist because these artifacts have a backend half and a frontend half. The shared language lives in $libs/*, the server authority lives in $svrs/*, and the reactive browser/client wrappers live in $arts/*. @@ -239,8 +239,8 @@ export const POST = async ({ request }) => { Identity proof, CSRF, password/recovery flows, session binding, device primitives and security events. - $svrs/permissions - createEnginePermissions(options) + $svrs/perm + createEnginePerms(options) Authorization decisions, policy evaluation, explanations, query plans and HTTP handlers for the active client. @@ -269,16 +269,16 @@ export const POST = async ({ request }) => { EnginecreateEngineAuth, EngineAuthCurrent view, password flows, CSRF, recovery, devices, OAuth/MFA primitives and event subscriptions. HandlerscreateAuthRouteHandlers, createSvelteKitAuthHandleHTTP/SvelteKit integration. Default route handlers cover current, CSRF, password, recovery and sign-out. AdapterscreateMemoryAuthAdapter, createDbAuthAdapter, password/crypto/mailer/test adaptersPersistence and mechanism ports without hard dependency on an ORM/provider. - IntegrationsAuthSessPort, AuthCachePort, AuthPermissionsPort, AuthHttpPortPorts for sess, cach, perm, http, timer and logr. + IntegrationsAuthSessPort, AuthCachePort, AuthPermsPort, AuthHttpPortPorts for sess, cach, perm, http, timer and logr. -

Permissions server

+

Perms server

- $svrs/permissions is the authorization authority. The browser can ask for a + $svrs/perm is the authorization authority. The browser can ask for a decision, but protected routes must still call the server engine.

- + @@ -291,7 +291,7 @@ export const POST = async ({ request }) => { - +
Runtimecheck, can, assertEvaluate one decision and enforce fail-closed server behavior.
Inspectionexplain, what, whoDebug/audit decisions and build UI action matrices from server truth.
Query supportfilter(), createSqlCompiler()Turn permissions into predicates or query plans where providers support it.
HTTPcreatePermissionHttpHandlersRemote bridge for ActivePermissions; dispatches check, batch, what and explain.
HTTPcreatePermHttpHandlersRemote bridge for ActivePerms; dispatches check, batch, what and explain.
@@ -365,7 +365,7 @@ export const POST = async ({ request }) => { src/svrs/auth/testServer auth flows.CSRF, password and error guards. - src/svrs/permissions/testPermission engine.Policy runtime and handler behavior. + src/svrs/perm/testPerm engine.Policy runtime and handler behavior. src/svrs/cache/testCache engine.Query, invalidation, explain and disposal. /test/ecosystemCross-module scenario.Client route that exercises active/server interactions where available. diff --git a/src/web/routes/active/get-started/ai-agents/+page.svelte b/src/web/routes/active/get-started/ai-agents/+page.svelte index 608e370..55487dc 100644 --- a/src/web/routes/active/get-started/ai-agents/+page.svelte +++ b/src/web/routes/active/get-started/ai-agents/+page.svelte @@ -24,7 +24,7 @@ import type { Logger } from '$libs/logger'; // Good: server authority comes from $svrs import { createEngineAuth } from '$svrs/auth'; -import { createEnginePermissions } from '$svrs/permissions'; +import { createEnginePerms } from '$svrs/perm'; import { createEngineCache } from '$svrs/cache'; // Good: active/client wrappers come from $arts aliases @@ -182,7 +182,7 @@ logger.debug('conn', 'reconnect scheduled', { name, attempt });`;

Security rules

  • auth proves identity; sess keeps continuity; perm decides access.
  • -
  • The browser never decides authorization. ActivePermissions is UX only.
  • +
  • The browser never decides authorization. ActivePerms is UX only.
  • Storage must not persist passwords, refresh tokens, OTPs, CSRF secrets or provider tokens.
  • Private cache entries must include actor, tenant or permission scope when session/permission data exists.
  • Server routes must resolve actor context from server session, not from request body.
  • diff --git a/src/web/routes/active/get-started/composition/+page.svelte b/src/web/routes/active/get-started/composition/+page.svelte index ec319f3..4e16f82 100644 --- a/src/web/routes/active/get-started/composition/+page.svelte +++ b/src/web/routes/active/get-started/composition/+page.svelte @@ -116,13 +116,13 @@ adom ─── fend \\ | / | conn Always-present - Logger, Lang, Formats, Frontend, Dom, Storage, Http, Timers, Cache + Logger, Lang, Format, Frontend, Dom, Storage, Http, Timers, Cache App-scoped App.Lang, App.Cache, … Scoped factories - Sium, Sess, Auth, Permissions, Connections + Sium, Sess, Auth, Perms, Connections Feature / page App.createActiveSession(), App.createSiumEngine(), … @@ -131,7 +131,7 @@ adom ─── fend \\ | / | conn

    - Sess, Auth and Permissions are singletons inside an + Sess, Auth and Perms are singletons inside an App. Calling their factory twice throws SessionAlreadyCreatedError, AappAlreadyCreatedError respectively. Connections is currently multi-instance — each call returns a fresh registry. @@ -141,9 +141,9 @@ adom ─── fend \\ | / | conn

    Disposal

    App.dispose() tears down every artifact in the right order - (consumers → providers): Auth and Permissions first, then + (consumers → providers): Auth and Perms first, then Connections, then Sess, then Cache, - Timers, Frontend, Dom, Formats, + Timers, Frontend, Dom, Format, Storage, Lang, and finally Logger. Subsequent calls are no-ops.

    @@ -156,7 +156,7 @@ adom ─── fend \\ | / | conn

    Locale propagation

    App.setLocale(locale) is the single source of truth. It propagates through - Lang first, which then notifies Formats and Frontend + Lang first, which then notifies Format and Frontend via the shared localeSource bridge, so every consumer ends up agreeing on the same BCP 47 tag.

    @@ -165,7 +165,7 @@ adom ─── fend \\ | / | conn lang="ts" code={`App.setLocale('es-MX'); // → Lang resolves to 'es-MX' (with fallback chain) -// → Formats picks up the new locale for numbers / currency / dates +// → Format picks up the new locale for numbers / currency / dates // → Frontend updates the document direction (LTR / RTL)`} /> diff --git a/src/web/routes/active/get-started/ecosystem/+page.svelte b/src/web/routes/active/get-started/ecosystem/+page.svelte index 89cd975..b9f2421 100644 --- a/src/web/routes/active/get-started/ecosystem/+page.svelte +++ b/src/web/routes/active/get-started/ecosystem/+page.svelte @@ -22,7 +22,7 @@ $active-app client composition root that wires the active ecosystem`; const Sium = App.createSiumEngine(); const Sess = App.createActiveSession({ storage, onRefresh, onRevoke }); const Auth = App.createActiveAuth({ initial: data.auth }); -const Permissions = App.createActivePermissions({ endpoint: '/api/permissions' }); +const Perms = App.createActivePerms({ endpoint: '/api/permissions' }); const Connections = App.createActiveConnections();`; const serverFlow = `const Auth = createEngineAuth({ @@ -30,7 +30,7 @@ const Connections = App.createActiveConnections();`; ports: { store, actors, sess, cache, logger, timer, crypto, passwordHasher, mailer } }); -const Permissions = createEnginePermissions({ +const Perms = createEnginePerms({ schema, policies, providers, @@ -47,10 +47,10 @@ const Cache = createEngineCache({ });`; const requestFlow = `Browser - → ActiveAuth / ActivePermissions / App.Http / ActiveCache + → ActiveAuth / ActivePerms / App.Http / ActiveCache → SvelteKit endpoint or action → $svrs/auth reads Auth current and session binding - → $svrs/permissions asserts actor can perform action + → $svrs/perm asserts actor can perform action → $svrs/cache serves or fetches scoped data → App.Http receives typed result → Active roots update snapshots and UI`; @@ -59,13 +59,13 @@ const Cache = createEngineCache({ → ActiveAuth obtains CSRF and posts to server → EngineAuth validates credential and binds session through sess port → Session state changes - → Permissions snapshot/cache must be invalidated for the new actor + → Perms snapshot/cache must be invalidated for the new actor → Cache private scopes change actorId / permissionHash → Connections can reauthenticate or disconnect through the App session bridge`; const localeFlow = `App.setLocale('ar-EG') → App.Lang.setLocale('ar-EG') - → App.Formats.setLocale('ar-EG') + → App.Format.setLocale('ar-EG') → App.Frontend sees localeSource change → Frontend updates dir when dir is auto → Dom.apply writes dir="rtl" to the configured target`; @@ -180,7 +180,7 @@ const Cache = createEngineCache({ createEngineXxx()You need deterministic, non-runes runtime logic.createEngineHttp, createEngineTimers, createEngineSium createActiveXxx()You need Svelte 5 reactive state and lifecycle.createActiveStorage, createActiveConnections - $svrs/createEngineXxx()The result must be server-authoritative.createEngineAuth, createEnginePermissions, createEngineCache + $svrs/createEngineXxx()The result must be server-authoritative.createEngineAuth, createEnginePerms, createEngineCache App.createXxx()You want App to inject shared services.App.createSiumEngine(), App.createActiveAuth() @@ -238,7 +238,7 @@ const Cache = createEngineCache({ Persist non-secret preferences or drafts.storsess, localStorage calls spread through pages. Keep logged-in continuity.sessauth alone. Prove identity or run login/recovery flows.$svrs/auth plus $authperm or client-only checks. - Decide if an actor can do something.$svrs/permissions plus $permissionsauth, roles hard-coded in UI. + Decide if an actor can do something.$svrs/perm plus $permauth, roles hard-coded in UI. Cache data with scopes and invalidation.$svrs/cache or $cachestor as a query cache. Schedule retries, refreshes or timeouts.timerraw setTimeout scattered across modules. Open realtime sockets and channels.conncustom WebSocket state in components. diff --git a/src/web/routes/active/get-started/installation/+page.svelte b/src/web/routes/active/get-started/installation/+page.svelte index 385e01e..936a366 100644 --- a/src/web/routes/active/get-started/installation/+page.svelte +++ b/src/web/routes/active/get-started/installation/+page.svelte @@ -26,7 +26,7 @@
    • SvelteKit 2.x with Svelte 5.x (runes mode).
    • TypeScript 5.4+ recommended for full inference.
    • -
    • A Node 22+ runtime if you plan to use the server-authoritative engines ($svrs/auth, $svrs/permissions, $svrs/cache).
    • +
    • A Node 22+ runtime if you plan to use the server-authoritative engines ($svrs/auth, $svrs/perm, $svrs/cache).

    Path aliases

    @@ -46,11 +46,11 @@ $cache: 'src/arts/cach', $connection: 'src/arts/conn', $frontend: 'src/arts/fend', - $formats: 'src/arts/fmts', + $format: 'src/arts/fmts', $http: 'src/arts/http', $lang: 'src/arts/lang', $logger: 'src/arts/logr', - $permissions: 'src/arts/perm', + $perm: 'src/arts/perm', $session: 'src/arts/sess', $sium: 'src/arts/sium', $storage: 'src/arts/stor', @@ -125,7 +125,7 @@ App.Logger.info('app.boot', 'Active app ready');`}

    Even with no options, every member of App is present. App.Lang falls back to a passthrough mono-locale, App.Logger to a console transport, - App.Formats to the default locale. Call sites stay uniform whether or not the + App.Format to the default locale. Call sites stay uniform whether or not the feature is configured.

    @@ -150,9 +150,9 @@ export const App = createActiveApp({

    - Sess, Auth and Permissions are factory-on-App + Sess, Auth and Perms are factory-on-App (App.createActiveSession(), App.createActiveAuth(), - App.createActivePermissions()) because their lifetime is feature/page-scoped, + App.createActivePerms()) because their lifetime is feature/page-scoped, not app-scoped. Calling each twice throws AlreadyCreatedError.

    diff --git a/src/web/routes/active/get-started/versioning/+page.svelte b/src/web/routes/active/get-started/versioning/+page.svelte index 00d088e..59bca5c 100644 --- a/src/web/routes/active/get-started/versioning/+page.svelte +++ b/src/web/routes/active/get-started/versioning/+page.svelte @@ -8,7 +8,7 @@ const App = createActiveApp(); App.Logger; App.Lang; -App.Formats; +App.Format; App.Frontend; App.Dom; App.Storage; @@ -22,14 +22,14 @@ App.createSiumEngine(); App.createActiveSession(); App.createActiveConnections(); App.createActiveAuth(); -App.createActivePermissions();`; +App.createActivePerms();`; const deprecation = `/** * @deprecated Use setCurrency('auto') or clearCurrency() instead. * Deprecated in 0.1.3. Earliest removal: 0.2.0. */ function resetCurrency(): void { - logger.warn('formats.currency.deprecated.reset_currency', { + logger.warn('format.currency.deprecated.reset_currency', { context: { replacement: 'clearCurrency' } }); clearCurrency(); @@ -106,7 +106,7 @@ function resetCurrency(): void {

    Scoped factories are stable entry points, but some child surfaces are still younger than the core roots. They may grow during 0.1.x, especially Auth, - Permissions, Connections and Sium. + Perms, Connections and Sium.

    diff --git a/src/web/routes/test/+page.svelte b/src/web/routes/test/+page.svelte index 6931a99..f5a1a16 100644 --- a/src/web/routes/test/+page.svelte +++ b/src/web/routes/test/+page.svelte @@ -13,7 +13,7 @@
    • /test/aapp — composición ActiveApp: locale BCP 47 único compartido - entre Lang, Formats, Frontend y Sium; logger central con captura UI. + entre Lang, Format, Frontend y Sium; logger central con captura UI.
    • /test/ecosystem — app demo de integración total: auth, @@ -35,7 +35,7 @@
    • /test/fmts — formatos localizados con - Formats: numbers, currency, units y dates usando locale compartido. + Format: numbers, currency, units y dates usando locale compartido.
    • /test/fend — frontend reactivo con @@ -80,7 +80,7 @@
    • /test/perm — authorization con - Permissions: policies allow/deny, handlers HTTP, cache cliente, + Perms: policies allow/deny, handlers HTTP, cache cliente, what(), explain(), SQL plan y componente <Can />.
    • diff --git a/src/web/routes/test/aapp/+page.svelte b/src/web/routes/test/aapp/+page.svelte index 5846ede..4d405f4 100644 --- a/src/web/routes/test/aapp/+page.svelte +++ b/src/web/routes/test/aapp/+page.svelte @@ -124,7 +124,7 @@ })) ); - // ── Formats demo ─────────────────────────────────────────────────────────── + // ── Format demo ─────────────────────────────────────────────────────────── const sampleNumber = 1234567.89; const sampleAmount = 1500.5; const sampleDate = new Date(2026, 3, 25, 14, 30); @@ -133,10 +133,10 @@ // Touch the locale so $derived picks it up reactively. void App.Lang.getLocale(); return { - locale: App.Formats.getLocale(), - number: App.Formats.numbers.format(sampleNumber), - currency: App.Formats.currency.format(sampleAmount), - date: App.Formats.dates.formatDateTime(sampleDate) + locale: App.Format.getLocale(), + number: App.Format.numbers.format(sampleNumber), + currency: App.Format.currency.format(sampleAmount), + date: App.Format.dates.formatDateTime(sampleDate) }; }); @@ -199,7 +199,7 @@

      1. Locale (single source of truth)

      App.setLocale(...) propagates to Lang, - Formats and Frontend through a shared + Format and Frontend through a shared LocaleSource. BCP 47 resolution runs per record on every Lang.t() call.

      @@ -258,7 +258,7 @@
      -

      3. Formats — locale flows through

      +

      3. Format — locale flows through

      Same locale string reaches Intl.NumberFormat and Intl.DateTimeFormat via the shared LocaleSource. @@ -266,7 +266,7 @@ - + diff --git a/src/web/routes/test/aapp/schema.ts b/src/web/routes/test/aapp/schema.ts index 319c312..a1d8aea 100644 --- a/src/web/routes/test/aapp/schema.ts +++ b/src/web/routes/test/aapp/schema.ts @@ -21,10 +21,10 @@ export const translations = { 'pt-BR': 'Demo do ActiveApp' }, subtitle: { - es: 'Locale único compartido entre Lang, Formats y Frontend.', - en: 'Single locale shared across Lang, Formats and Frontend.', - ar: 'لغة واحدة مشتركة بين Lang و Formats و Frontend.', - 'pt-BR': 'Locale único compartilhado entre Lang, Formats e Frontend.' + es: 'Locale único compartido entre Lang, Format y Frontend.', + en: 'Single locale shared across Lang, Format and Frontend.', + ar: 'لغة واحدة مشتركة بين Lang و Format و Frontend.', + 'pt-BR': 'Locale único compartilhado entre Lang, Format e Frontend.' }, greeting: (params: { name: string }) => ({ es: `Hola ${params.name}`, diff --git a/src/web/routes/test/ecosystem/+page.svelte b/src/web/routes/test/ecosystem/+page.svelte index b3baaf3..d7d179b 100644 --- a/src/web/routes/test/ecosystem/+page.svelte +++ b/src/web/routes/test/ecosystem/+page.svelte @@ -22,8 +22,8 @@ import type { SupportedLocale } from '$lang'; import { LogLevel, type LogEntry, type Transport } from '$logger'; import { - PERMISSION_EFFECT_ALLOW, - PERMISSION_EFFECT_DENY, + PERM_EFFECT_ALLOW, + PERM_EFFECT_DENY, actor, allow, and, @@ -32,17 +32,17 @@ definePolicies, deny, rel, - setPermissionsContext, - type PermissionDecision, + setPermsContext, + type PermDecision, type ResourceRef, type SubjectRef - } from '$permissions'; - import { createEnginePermissions, createPermissionHttpHandlers } from '$svrs/permissions'; + } from '$perm'; + import { createEnginePerms, createPermHttpHandlers } from '$svrs/perm'; import { siumLangs, type Issue } from '$sium'; import { createMemoryAdapter } from '$storage'; const DEMO_PATH = '/test/ecosystem'; - const PERMISSION_ENDPOINT = 'https://ecosystem.local/api/permissions'; + const PERM_ENDPOINT = 'https://ecosystem.local/api/permissions'; const HTTP_PROJECT_PATH = '/api/demo/project'; const LOGGER_CATEGORY_ECOSYSTEM = 'test.ecosystem'; const LOG_SCENARIO_STARTED = 'ecosystem.scenario.started'; @@ -257,7 +257,7 @@ let validationIssues = $state([]); let projectFromCache = $state(null); let cacheExplain = $state(null); - let lastDecision = $state(null); + let lastDecision = $state(null); let receivedUpdates = $state([]); let timerFiredCount = $state(0); let authRoundtripStatus = $state('initial'); @@ -293,7 +293,7 @@ } }); - const PermEngine = createEnginePermissions({ + const PermEngine = createEnginePerms({ schema: permissionSchema, policies: permissionPolicies, providers: { @@ -307,7 +307,7 @@ } }); - const permHandlers = createPermissionHttpHandlers(PermEngine, () => currentActor); + const permHandlers = createPermHttpHandlers(PermEngine, () => currentActor); const demoFetch = (async (input: RequestInfo | URL, init?: RequestInit): Promise => { const path = requestPath(input); @@ -444,9 +444,9 @@ expiresAt: initialData.sessionSeed.expiresAt }); - const Permissions = setPermissionsContext( - App.createActivePermissions({ - endpoint: PERMISSION_ENDPOINT, + const Perms = setPermsContext( + App.createActivePerms({ + endpoint: PERM_ENDPOINT, cacheTtlMs: 8_000, scopeKey: () => `${data.sessionSeed.actorId}:${actorRole}:${mfaEnabled}` }) @@ -527,10 +527,10 @@ void App.Lang.getLocale(); const payload = projectFromCache ?? createProjectPayload(); return { - budget: App.Formats.currency.format(payload.budget), - version: App.Formats.numbers.format(payload.version), - distance: App.Formats.units.formatDefault(12, 'distance'), - date: App.Formats.dates.formatDateTime(new Date(payload.fetchedAt)) + budget: App.Format.currency.format(payload.budget), + version: App.Format.numbers.format(payload.version), + distance: App.Format.units.formatDefault(12, 'distance'), + date: App.Format.dates.formatDateTime(new Date(payload.fetchedAt)) }; }); @@ -571,7 +571,7 @@ pushEvent('auth', `current -> ${authRoundtripStatus}`); await validateDraft(); - await runPermission(PROJECT_ACTION_UPDATE); + await runPerm(PROJECT_ACTION_UPDATE); await loadProject(); scheduleSyncTimer(); await publishRealtimeUpdate(); @@ -588,13 +588,13 @@ pushEvent('sium', result.ok ? 'draft válido' : `${result.issues.length} issue(s)`); } - async function runPermission(action = PROJECT_ACTION_UPDATE): Promise { - lastDecision = await Permissions.check({ + async function runPerm(action = PROJECT_ACTION_UPDATE): Promise { + lastDecision = await Perms.check({ action, resource: currentResource, context: permissionContext }); - pushEvent('permissions', `${action} -> ${decisionLabel(lastDecision)}`); + pushEvent('perm', `${action} -> ${decisionLabel(lastDecision)}`); } async function loadProject(): Promise { @@ -655,7 +655,7 @@ async function resetDataPlane(): Promise { await App.Cache.clear(); - Permissions.invalidate(); + Perms.invalidate(); projectFromCache = null; cacheExplain = null; lastDecision = null; @@ -748,15 +748,15 @@ }); } - function decisionLabel(decision: PermissionDecision | null | undefined): string { + function decisionLabel(decision: PermDecision | null | undefined): string { if (!decision) return 'sin decisión'; return decision.effect; } - function decisionClass(decision: PermissionDecision | null | undefined): string { + function decisionClass(decision: PermDecision | null | undefined): string { if (!decision) return 'idle'; - if (decision.effect === PERMISSION_EFFECT_ALLOW) return 'allow'; - if (decision.effect === PERMISSION_EFFECT_DENY) return 'deny'; + if (decision.effect === PERM_EFFECT_ALLOW) return 'allow'; + if (decision.effect === PERM_EFFECT_DENY) return 'deny'; return 'warn'; } @@ -815,7 +815,7 @@ value={actorRole} onchange={(event) => { actorRole = event.currentTarget.value as typeof ROLE_ADMIN | typeof ROLE_VIEWER; - Permissions.invalidate(); + Perms.invalidate(); }} > @@ -828,7 +828,7 @@ checked={mfaEnabled} onchange={(event) => { mfaEnabled = event.currentTarget.checked; - Permissions.invalidate(); + Perms.invalidate(); }} /> MFA activo @@ -910,7 +910,7 @@
      - +
      @@ -929,7 +929,7 @@
      -

      Formats + Frontend + Dom

      +

      Format + Frontend + Dom

      budget{formattedProject.budget}
      version{formattedProject.version}
      @@ -939,7 +939,7 @@
      breakpoint{shellView.breakpoint}

      - Al cambiar el locale, Lang, Formats y Frontend comparten la misma fuente. Con arar el shell pasa a rtl.

      diff --git a/src/web/routes/test/fmts/+page.svelte b/src/web/routes/test/fmts/+page.svelte index fee769a..9194a8f 100644 --- a/src/web/routes/test/fmts/+page.svelte +++ b/src/web/routes/test/fmts/+page.svelte @@ -1,6 +1,6 @@
      Formats.getLocale()Format.getLocale() {formatsView.locale}