Add arts/timr, arts/sess, arts/http; sess actor extension; aapp factory
New artifacts:
arts/timr — runtime timer scheduler (singleton-per-App, never global).
Engine + Active split. Race-safe via (id, key, version) guard against
stale native callbacks, async tasks resolving after cancel/replace,
interval ticks scheduled after dispose, and ack-style timeouts. Per-
entry AbortController; intervals reuse the signal across ticks.
awaitTask:true (default, no overlap) vs awaitTask:false (fire-and-
forget cadence; failures don't stop the interval — semantic frozen).
Recursive setTimeout for intervals. scheduleAt(past) → delay 0 (no
error). Fake-clock injectable for deterministic tests. Pure
computeBackoffDelay helper. 50 server tests + 7 browser tests + full
README + DESIGN_TIMR + interactive test page at /test/timr.
arts/sess — session lifecycle with three generics
(TUser/TCredential/TData) plus optional SessionActor metadata
(kind/source/confidence — orthogonal axis to identity).
Tagged AdoptResult/RefreshResult/RevokeResult; never void.
Generation guard against stale refresh from local cancel, cross-tab
storage events, or re-adopt. onRefresh contract (null=fatal,
throw=transient). onRevoke replaces revokeUrl (consumer controls
fetch; engine just gets boolean). Default scope: 'global' when
onRevoke configured. INITIAL_SESSION sync dispatch on subscribe.
BroadcastChannel payload: {type, event, generation} only — never
tokens. SSR cookie reader validates invariants. withAutoRefresh
helper, 401-retry hook with applyAuth + loop guard, JWT exp helper.
103 tests (60 server + 5 browser + 38 actor/error/etc) + README +
DESIGN.md + test page at /test/sess.
arts/http — HTTP client with Standard Schema body validation, retry
+ Retry-After, attempt + total timeout via AbortSignal composition,
hooks (beforeRequest/beforeRetry/afterResponse/beforeError),
tagged HttpResult. Test page at /test/http.
arts/conn — DESIGN_CONN.md only (no implementation yet).
aapp:
- App.createActiveSession<TUser, TCredential, TData>(opts) factory
auto-injects App.Logger; throws SessAlreadyCreatedError on second
call. App.Sess getter exposes the active session (undefined until
first call). Auto-disposed by App.dispose().
- App.Timers integration deferred to timr Fase 3.
libs/days:
- toEpochMs(value) — permissive coercion (number | Date | string) →
epoch ms. Exposed alongside the existing day/calendar helpers.
Other:
- sium examples moved to _examples/ (excluded from public surface).
- sium-provider.svelte test removed (port pending; tracked elsewhere).
- libs/env.ts — DEV flag single source of truth.
- Various README touch-ups across artifacts.
All artifacts: 1021 server tests + 12 browser tests; svelte-check + ESLint
clean. The 9 server "errors" are pre-existing jsdom missing — unrelated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
# http
|
|
|
|
|
|
|
|
|
|
Professional HTTP client. **Zero external dependencies.** Tagged-result error
|
|
|
|
|
model. Per-call schema validation against any Standard Schema (Sium, Zod,
|
|
|
|
|
Valibot, ArkType). Idempotent-by-default retry with `Retry-After` parsing.
|
|
|
|
|
Per-attempt and total-budget timeouts. SvelteKit-native via injectable
|
|
|
|
|
`fetch`. Hooks for auth refresh, logging, transformation.
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
import { createEngineHttp } from '$http';
|
|
|
|
|
|
|
|
|
|
const http = createEngineHttp({ baseUrl: 'https://api.example.com' });
|
|
|
|
|
|
|
|
|
|
const r = await http.get('/users/me', { schema: UserSchema });
|
|
|
|
|
|
|
|
|
|
if (r.ok) {
|
|
|
|
|
console.log(r.value.email);
|
|
|
|
|
} else if (r.kind === 'http') {
|
|
|
|
|
console.error(`HTTP ${r.status}: ${r.statusText}`);
|
|
|
|
|
} else if (r.kind === 'validation') {
|
|
|
|
|
console.error('Server returned an unexpected shape:', r.issues);
|
|
|
|
|
} else {
|
|
|
|
|
console.error('Network failure:', r.error);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Why another one
|
|
|
|
|
|
|
|
|
|
`fetch` exists. `axios`, `ky`, `ofetch`, `wretch` exist. None ship with the
|
|
|
|
|
combination this artifact targets:
|
|
|
|
|
|
|
|
|
|
- **Zero dependencies.** `axios` carries IE polyfills, `wretch` ships
|
|
|
|
|
middlewares as separate addons, `ofetch` depends on `unjs/ufo`.
|
|
|
|
|
- **Tagged-result error model.** Every other library throws on non-2xx.
|
|
|
|
|
`arts/http` returns a discriminated union — errors are data, not exceptions
|
|
|
|
|
(matches `$sium`'s `Result<O>` for uniform pattern matching).
|
|
|
|
|
- **Standard Schema first-class.** Per-call `schema` parameter validates the
|
|
|
|
|
response body. Returns `Out<S>` typed. Works with Sium / Zod / Valibot /
|
|
|
|
|
ArkType identically. No runtime dep on Sium — the artifact only imports
|
|
|
|
|
the `StandardSchemaV1` interface from `$libs/standard-schema`.
|
|
|
|
|
- **`bodySchema` for write methods.** Validate the request payload before
|
|
|
|
|
it leaves the engine. Catches "I sent the wrong shape" — a class of bugs
|
|
|
|
|
no other generic HTTP client catches.
|
|
|
|
|
- **SvelteKit-aware.** `engine.with({ fetch: event.fetch })` returns a scoped
|
|
|
|
|
client that inherits cookies, resolves relative URLs and skips network
|
|
|
|
|
roundtrips when the route is local.
|
|
|
|
|
- **Retryer extracted.** The retry primitive is a separable export — a
|
|
|
|
|
future `arts/cache` can reuse it without a duplicate implementation.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Architecture
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
http/
|
|
|
|
|
├── index.ts Barrel exports
|
|
|
|
|
├── types.ts EngineHttp, HttpResult, HttpInit, HttpHooks, RetryConfig
|
|
|
|
|
├── consts.ts DEFAULT_RETRY, DEFAULT_TIMEOUT, NULL_BODY_STATUSES, ...
|
|
|
|
|
├── engine-http.ts Factory createEngineHttp()
|
|
|
|
|
├── errors.ts HttpNetworkError / HttpTimeoutError / HttpAbortError /
|
|
|
|
|
│ HttpBodyValidationError + type guards
|
|
|
|
|
├── retry.ts parseRetryAfter, shouldRetryRequest, computeRetryDelay,
|
|
|
|
|
│ delayWithSignal (reusable by arts/cache)
|
|
|
|
|
├── timeout.ts composeSignals, attemptTimeoutSignal, totalTimeoutSignal,
|
|
|
|
|
│ classifyAbort
|
|
|
|
|
├── body.ts isJSONSerializable, serializeBody, parseBody, mergeHeaders
|
|
|
|
|
├── search.ts normalizeSearch, appendSearch, resolveUrl
|
|
|
|
|
└── test/
|
|
|
|
|
├── engine-http.test.ts end-to-end with stub fetch
|
|
|
|
|
├── retry.test.ts Retry-After parsing, policy, delay
|
|
|
|
|
├── timeout.test.ts signal composition + classification
|
|
|
|
|
├── body.test.ts JSON serialization + content-type sniffing
|
|
|
|
|
├── search.test.ts search + URL helpers
|
|
|
|
|
└── errors.test.ts hierarchy + type guards
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Alias
|
|
|
|
|
|
|
|
|
|
Configured in `svelte.config.js`:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
alias: { $http: 'src/arts/http' }
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## HttpResult — the discriminated union
|
|
|
|
|
|
|
|
|
|
Every method returns `Promise<HttpResult<T>>`:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
type HttpResult<T> =
|
|
|
|
|
| { ok: true; value: T; response: Response }
|
|
|
|
|
| { ok: false; kind: 'http'; status; statusText; body; response: Response }
|
|
|
|
|
| { ok: false; kind: 'validation'; issues; response: Response }
|
|
|
|
|
| { ok: false; kind: 'network'; error };
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The `ok` discriminator matches Sium's. Pattern-match identically:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
if (r.ok) {
|
|
|
|
|
use(r.value);
|
|
|
|
|
} else switch (r.kind) {
|
|
|
|
|
case 'http': toast(`Server returned ${r.status}`); break;
|
|
|
|
|
case 'validation': console.error(r.issues); break;
|
|
|
|
|
case 'network': toast('Offline?'); break;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The `response: Response` is preserved on the three branches that have one —
|
|
|
|
|
auth flows, rate-limit headers and ETags all live there. The `network`
|
|
|
|
|
branch deliberately omits `response` because no HTTP exchange happened.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Validation — opt-in via `schema`
|
|
|
|
|
|
|
|
|
|
No schema → `value` is `unknown`, body is not parsed:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
const r = await http.get('/api/health'); // r.value: unknown (undefined)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
With a schema → body is parsed (JSON or text) and validated:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
const UserSchema = sium.object({ id: sium.string(), email: sium.email() });
|
|
|
|
|
|
|
|
|
|
const r = await http.get('/api/users/me', { schema: UserSchema });
|
|
|
|
|
if (r.ok) r.value.email; // string, validated
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Works with any Standard Schema vendor:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
import { z } from 'zod';
|
|
|
|
|
const r = await http.get('/api/posts', {
|
|
|
|
|
schema: z.array(z.object({ id: z.string() }))
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
For `POST` / `PUT` / `PATCH`, validate the request body too:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
await http.post('/api/users', {
|
|
|
|
|
body: payload,
|
|
|
|
|
bodySchema: CreateUserSchema, // pre-flight check; throws if invalid
|
|
|
|
|
schema: UserSchema // post-flight check on response
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
When `bodySchema` rejects, the engine throws `HttpBodyValidationError`
|
|
|
|
|
synchronously. This is a programmer error (you sent the wrong shape), not
|
|
|
|
|
a runtime data condition — different from `kind: 'validation'` which is
|
|
|
|
|
about the *server's* response.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## API
|
|
|
|
|
|
|
|
|
|
### `createEngineHttp(options?)`
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
const http = createEngineHttp({
|
|
|
|
|
baseUrl: 'https://api.example.com',
|
|
|
|
|
headers: { Accept: 'application/json' },
|
|
|
|
|
fetch: globalThis.fetch, // override per-engine
|
|
|
|
|
timeout: 10_000, // per-attempt (ms)
|
|
|
|
|
totalTimeout: 30_000, // total budget covering all attempts (0 = off)
|
|
|
|
|
retry: {
|
|
|
|
|
limit: 2,
|
|
|
|
|
statusCodes: [408, 425, 429, 500, 502, 503, 504],
|
|
|
|
|
methods: ['GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS'], // idempotent
|
|
|
|
|
delay: (n) => 1000 * 2 ** (n - 1), // exponential
|
|
|
|
|
backoffLimit: 30_000,
|
|
|
|
|
jitter: false
|
|
|
|
|
},
|
|
|
|
|
hooks: {
|
|
|
|
|
beforeRequest: [],
|
|
|
|
|
beforeRetry: [],
|
|
|
|
|
afterResponse: [],
|
|
|
|
|
beforeError: []
|
|
|
|
|
},
|
Prefs as schema-based core + lowercase App.* surface
Two structural changes that were overdue and got bundled because
they touched the same set of files.
## Prefs is now a schema, not a fixed shape
Previously every preference had to be declared in a closed
`PrefsCapabilities` interface (`languages`, `locales`, `currencies`,
`themes`, `densities`, `motions`, `timezones`, `unitSystems`). Adding
a new pref required forking `$libs/prefs` — bad framework design.
The redesign replaces the fixed shape with a schema:
PrefsSchema = Record<string, PrefsDimension<TIntent, TEffective>>
Each dimension owns its own validator (`validate`), environment-fed
resolver (`resolve`) and optional sibling-derived value (`derive`).
The engine is generic over the schema and iterates it; it knows
nothing about "locale" or "theme" specifically.
Built-in dimensions live in `arts/prefs/dimensions/*` (locale,
language, theme, density, motion, timezone, currency, unit-system,
direction, plus boolean / enum / string / number primitives). The
`standardPrefsDimensions(catalog)` preset composes the canonical
set; apps spread it and add their own:
const schema = {
...standardPrefsDimensions({ languages, locales, currencies }),
sidebarCollapsed: booleanDimension({ default: false }),
notificationLevel: enumDimension(
['all', 'mentions', 'none'] as const,
{ default: 'mentions' }
)
};
Active surface exposes one slot per schema key with uniform verbs:
App.prefs.locale.get()
App.prefs.locale.set('es-ES')
App.prefs.locale.clear()
App.prefs.locale.onChange((v) => …)
App.prefs.sidebarCollapsed.set(true)
`setIntent('locale', value)` stays available as a low-level pass-
through (storage bridge consumes it generically) but UI code uses
the dimension surface.
## Lowercase core surface
`App.Logger`, `App.Bus`, `App.Timers`, `App.Orca`, `App.Prefs` are
gone. The "PascalCase for core, lowercase for services" rule was
visual signalling against JS convention, no technical benefit, and
created an asymmetry on the same object. All core members are now
lowercase, matching services:
App.logger
App.bus
App.timers
App.orca
App.prefs
`createPrefsStorageBridge` keeps its old responsibilities; sources
helpers (`prefsLocaleSource`, …) are gone — the dimension API
replaces them.
## What changed
- `$libs/prefs`: fully generic schema-based types + resolver. Old
fixed `PrefsCapabilities` / `PrefsIntent` / `PrefsEffective`
removed; replaced by `PrefsDimension`, `PrefsSchema`,
`PrefsEffectiveOf<S>`, `PrefsIntentOf<S>`.
- `arts/prefs`: engine + active wrapper rewritten to schema. Per-
dimension active surface auto-built from schema keys. Sources file
deleted (replaced by dimension surface). New
`arts/prefs/dimensions/*` and `arts/prefs/standard.ts`. Storage
bridge made schema-generic.
- `arts/active-app`: lowercase `CoreServices` / `ActiveAppCore`,
`prefs?: ActiveAppPrefsOptions<S>` root option carrying the
schema. `defineActivePrefs` deleted (prefs is core, not service).
`lang` / `format` / `frontend` factories migrated to read
`core.prefs.<dim>` directly via defensive `readSlot()` helpers
(each dimension is optional from the factory's POV; if the app's
schema omits one, the integration degrades gracefully).
- Presets, demos, web routes, README docstrings, `check-aliases.mjs`
guards, marketing snippets all migrated.
- Tests: `engine-prefs`, `active-prefs`, `storage-bridge`,
`resolve-prefs`, `validate-intent`, `prefs-consumer-wiring`,
`service-factories` rewritten for the schema-based API.
`sources.test.ts` deleted (sources file is gone).
## Verification
- `npm run check`: 0 errors, 0 warnings (1527 files).
- `npm test`: 1645 tests across 139 files, all green.
- `node scripts/check-aliases.mjs`: clean (lowercase enforced for
every member of `App.*`, including `Logger`/`Bus`/`Timers`/`Orca`/
`Prefs` which now flag as forbidden capitals).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
logger: App.logger // wired automatically when used via App.http
|
Add arts/timr, arts/sess, arts/http; sess actor extension; aapp factory
New artifacts:
arts/timr — runtime timer scheduler (singleton-per-App, never global).
Engine + Active split. Race-safe via (id, key, version) guard against
stale native callbacks, async tasks resolving after cancel/replace,
interval ticks scheduled after dispose, and ack-style timeouts. Per-
entry AbortController; intervals reuse the signal across ticks.
awaitTask:true (default, no overlap) vs awaitTask:false (fire-and-
forget cadence; failures don't stop the interval — semantic frozen).
Recursive setTimeout for intervals. scheduleAt(past) → delay 0 (no
error). Fake-clock injectable for deterministic tests. Pure
computeBackoffDelay helper. 50 server tests + 7 browser tests + full
README + DESIGN_TIMR + interactive test page at /test/timr.
arts/sess — session lifecycle with three generics
(TUser/TCredential/TData) plus optional SessionActor metadata
(kind/source/confidence — orthogonal axis to identity).
Tagged AdoptResult/RefreshResult/RevokeResult; never void.
Generation guard against stale refresh from local cancel, cross-tab
storage events, or re-adopt. onRefresh contract (null=fatal,
throw=transient). onRevoke replaces revokeUrl (consumer controls
fetch; engine just gets boolean). Default scope: 'global' when
onRevoke configured. INITIAL_SESSION sync dispatch on subscribe.
BroadcastChannel payload: {type, event, generation} only — never
tokens. SSR cookie reader validates invariants. withAutoRefresh
helper, 401-retry hook with applyAuth + loop guard, JWT exp helper.
103 tests (60 server + 5 browser + 38 actor/error/etc) + README +
DESIGN.md + test page at /test/sess.
arts/http — HTTP client with Standard Schema body validation, retry
+ Retry-After, attempt + total timeout via AbortSignal composition,
hooks (beforeRequest/beforeRetry/afterResponse/beforeError),
tagged HttpResult. Test page at /test/http.
arts/conn — DESIGN_CONN.md only (no implementation yet).
aapp:
- App.createActiveSession<TUser, TCredential, TData>(opts) factory
auto-injects App.Logger; throws SessAlreadyCreatedError on second
call. App.Sess getter exposes the active session (undefined until
first call). Auto-disposed by App.dispose().
- App.Timers integration deferred to timr Fase 3.
libs/days:
- toEpochMs(value) — permissive coercion (number | Date | string) →
epoch ms. Exposed alongside the existing day/calendar helpers.
Other:
- sium examples moved to _examples/ (excluded from public surface).
- sium-provider.svelte test removed (port pending; tracked elsewhere).
- libs/env.ts — DEV flag single source of truth.
- Various README touch-ups across artifacts.
All artifacts: 1021 server tests + 12 browser tests; svelte-check + ESLint
clean. The 9 server "errors" are pre-existing jsdom missing — unrelated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### `engine.with(overrides)`
|
|
|
|
|
|
|
|
|
|
Returns a new engine with `overrides` merged on top of the current defaults.
|
|
|
|
|
Hook arrays concatenate (parent first); scalar fields are replaced. **Never
|
|
|
|
|
mutates** the parent engine.
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
const scoped = http.with({ fetch: event.fetch }); // SvelteKit pattern
|
|
|
|
|
const apiV2 = http.with({ baseUrl: 'https://api.example.com/v2' });
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Method shortcuts
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
http.get <S>(url, init?) → Promise<HttpResult<Out<S>>>
|
|
|
|
|
http.head <S>(url, init?) → Promise<HttpResult<Out<S>>>
|
|
|
|
|
http.delete<S>(url, init?) → Promise<HttpResult<Out<S>>>
|
|
|
|
|
http.options<S>(url, init?) → Promise<HttpResult<Out<S>>>
|
|
|
|
|
http.post <S, B>(url, init?) → Promise<HttpResult<Out<S>>>
|
|
|
|
|
http.put <S, B>(url, init?) → Promise<HttpResult<Out<S>>>
|
|
|
|
|
http.patch <S, B>(url, init?) → Promise<HttpResult<Out<S>>>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`Out<S>` is `unknown` when `S` is undefined, otherwise `InferOutput<S>`.
|
|
|
|
|
|
|
|
|
|
### Per-call init
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
http.get('/users', {
|
|
|
|
|
schema: UserSchema, // validate response body
|
|
|
|
|
bodySchema: NewUserSchema, // (write methods only) validate body
|
|
|
|
|
body: { name: 'Ada' }, // any HttpBodyInit
|
|
|
|
|
headers: { 'X-Trace-Id': '...' }, // merged on top of engine defaults
|
|
|
|
|
search: { page: 2, q: 'ada' }, // appended to URL
|
|
|
|
|
fetch: event.fetch, // per-call fetch override
|
|
|
|
|
timeout: 5_000, // per-attempt (ms)
|
|
|
|
|
signal: abortCtrl.signal, // user signal — aborts cancel retry
|
|
|
|
|
retry: { limit: 0 }, // override retry policy (or `false`)
|
|
|
|
|
hooks: { beforeRequest: [trace] } // per-call hooks (engine first)
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Retry policy
|
|
|
|
|
|
|
|
|
|
Defaults — *idempotent-by-default*: `GET`, `HEAD`, `PUT`, `DELETE`,
|
|
|
|
|
`OPTIONS` retry; `POST` and `PATCH` do not (unless `methods` is overridden).
|
|
|
|
|
Status codes: `408 425 429 500 502 503 504`.
|
|
|
|
|
|
|
|
|
|
### Retry-After
|
|
|
|
|
|
|
|
|
|
Five header variants are parsed in priority order:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
Retry-After, RateLimit-Reset, X-RateLimit-Reset,
|
|
|
|
|
X-Rate-Limit-Reset, X-RateLimit-Retry-After
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Numeric values below `1.7e9` are treated as delta-seconds; above as
|
|
|
|
|
epoch-seconds (the threshold is "no realistic retry delay is that large; no
|
|
|
|
|
realistic past timestamp is that small"). HTTP-dates (`Sun, 06 Nov 1994
|
|
|
|
|
08:49:37 GMT`) are also accepted. When the server signals a delay, it
|
|
|
|
|
overrides the policy's `delay(attempt)`.
|
|
|
|
|
|
|
|
|
|
### Signal hierarchy (no surprises)
|
|
|
|
|
|
|
|
|
|
| Signal source | Reaction |
|
|
|
|
|
| ------------------------- | ---------------------------------------------- |
|
|
|
|
|
| User-provided `signal` | Aborts the current attempt; **no retry** |
|
|
|
|
|
| `totalTimeout` exceeded | Aborts the current attempt; **no retry** |
|
|
|
|
|
| Per-attempt `timeout` | Aborts the attempt; **retries** if budget left |
|
|
|
|
|
|
|
|
|
|
Internal classification uses `signal.reason` (an `HttpTimeoutError` with
|
|
|
|
|
`scope: 'attempt' | 'total'`, or an `HttpAbortError` for user signals) so
|
|
|
|
|
the retryer makes the right call without ambiguity.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Hooks lifecycle
|
|
|
|
|
|
|
|
|
|
Per attempt, in order:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
beforeRequest[0..k] → fetch() → afterResponse[0..k] (success)
|
|
|
|
|
↘ beforeRetry[0..k] → wait(delay) → next attempt
|
|
|
|
|
|
|
|
|
|
Final failure: → beforeError[0..k] (may rescue with a Response)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Mutate `ctx.request.headers` in place — the change persists into the
|
|
|
|
|
`fetch` call. To rewrite the URL, mutate `ctx.url`. To short-circuit with a
|
|
|
|
|
cached response, return it from `beforeRequest`. To rescue a failed attempt,
|
|
|
|
|
return a `Response` from `beforeError` and the engine resolves as if it had
|
|
|
|
|
been received cleanly.
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
const http = createEngineHttp({
|
|
|
|
|
hooks: {
|
|
|
|
|
beforeRequest: [
|
|
|
|
|
(ctx) => {
|
|
|
|
|
ctx.request.headers.set('X-Trace-Id', crypto.randomUUID());
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
beforeRetry: [
|
|
|
|
|
({ attempt, retryDelay, error }) => {
|
|
|
|
|
console.warn(`retry ${attempt}, waiting ${retryDelay}ms`, error);
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
afterResponse: [
|
|
|
|
|
({ response }) => {
|
|
|
|
|
metrics.observe('http.duration', response.headers.get('x-time'));
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
beforeError: [
|
|
|
|
|
({ response }) => {
|
|
|
|
|
if (response?.status === 401) {
|
|
|
|
|
return refreshAndRetry(); // returns a new Response or undefined
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Auth refresh pattern
|
|
|
|
|
|
|
|
|
|
Use a header **hook** so the token is read per attempt — `beforeRetry` can
|
|
|
|
|
update the underlying token between attempts and the next attempt's
|
|
|
|
|
headers reflect the new value:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
let token = await getToken();
|
|
|
|
|
|
|
|
|
|
const http = createEngineHttp({
|
|
|
|
|
headers: () => ({ Authorization: `Bearer ${token}` }),
|
|
|
|
|
hooks: {
|
|
|
|
|
beforeRetry: [
|
|
|
|
|
async ({ error, response }) => {
|
|
|
|
|
if (response?.status === 401) {
|
|
|
|
|
token = await refreshToken();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## SvelteKit integration
|
|
|
|
|
|
|
|
|
|
In a `+page.ts` / `+page.server.ts` / `+layout.ts` `load`, scope the engine
|
|
|
|
|
to the request via `event.fetch`:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
// +page.server.ts
|
|
|
|
|
import type { PageServerLoad } from './$types';
|
|
|
|
|
|
|
|
|
|
export const load: PageServerLoad = async ({ fetch }) => {
|
Bloque A — sweep stale aliases and `App.<Capitalized>` references
Audit P1: documentation must stop teaching APIs the runtime no
longer exposes. The svelte.config.js aliases are full words now
(`$cache`, `$session`, `$connection`, `$timer`, `$logger`,
`$format`, `$storage`, `$active-app`, `$bus`); the legacy 4-letter
forms (`$cach`, `$sess`, `$conn`, `$timr`, `$logr`, `$fmts`,
`$stor`, `$aapp`, `$buss`) were retired earlier but still lived in
READMEs, demo pages, comments and a few code docstrings.
Likewise, the `App.<service>` surface is lowercase for declarable
services. The capitalized form is reserved for the four-piece core
(`Logger`, `Bus`, `Timers`, `Orca`). References like `App.Cache`,
`App.Sess`, `App.Storage`, `App.Format`, `App.Frontend`, `App.Lang`,
`App.Auth`, `App.Perms`, `App.Http`, `App.Dom`, `App.Sium` were
either ported to the new lowercase or migrated where it made sense.
Mechanical sweep across `src/`, then a guard script:
- `scripts/check-aliases.mjs` walks `src/`, fails the run if any
forbidden alias or `App.<forbidden capitalized>` appears in any
`.ts` / `.svelte` / `.md` / `.txt` / `.js` / `.mjs` file.
`arts/active-app/types.ts` is allowlisted because its block
comment explicitly documents the legacy uppercase surface as
"removed".
- `npm run test:aliases` exposes the script.
- `npm run test:all` now includes the alias check.
No runtime change; tests still 1486 / 1486.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
const api = App.http.with({ fetch }); // inherits cookies + relative URLs
|
Add arts/timr, arts/sess, arts/http; sess actor extension; aapp factory
New artifacts:
arts/timr — runtime timer scheduler (singleton-per-App, never global).
Engine + Active split. Race-safe via (id, key, version) guard against
stale native callbacks, async tasks resolving after cancel/replace,
interval ticks scheduled after dispose, and ack-style timeouts. Per-
entry AbortController; intervals reuse the signal across ticks.
awaitTask:true (default, no overlap) vs awaitTask:false (fire-and-
forget cadence; failures don't stop the interval — semantic frozen).
Recursive setTimeout for intervals. scheduleAt(past) → delay 0 (no
error). Fake-clock injectable for deterministic tests. Pure
computeBackoffDelay helper. 50 server tests + 7 browser tests + full
README + DESIGN_TIMR + interactive test page at /test/timr.
arts/sess — session lifecycle with three generics
(TUser/TCredential/TData) plus optional SessionActor metadata
(kind/source/confidence — orthogonal axis to identity).
Tagged AdoptResult/RefreshResult/RevokeResult; never void.
Generation guard against stale refresh from local cancel, cross-tab
storage events, or re-adopt. onRefresh contract (null=fatal,
throw=transient). onRevoke replaces revokeUrl (consumer controls
fetch; engine just gets boolean). Default scope: 'global' when
onRevoke configured. INITIAL_SESSION sync dispatch on subscribe.
BroadcastChannel payload: {type, event, generation} only — never
tokens. SSR cookie reader validates invariants. withAutoRefresh
helper, 401-retry hook with applyAuth + loop guard, JWT exp helper.
103 tests (60 server + 5 browser + 38 actor/error/etc) + README +
DESIGN.md + test page at /test/sess.
arts/http — HTTP client with Standard Schema body validation, retry
+ Retry-After, attempt + total timeout via AbortSignal composition,
hooks (beforeRequest/beforeRetry/afterResponse/beforeError),
tagged HttpResult. Test page at /test/http.
arts/conn — DESIGN_CONN.md only (no implementation yet).
aapp:
- App.createActiveSession<TUser, TCredential, TData>(opts) factory
auto-injects App.Logger; throws SessAlreadyCreatedError on second
call. App.Sess getter exposes the active session (undefined until
first call). Auto-disposed by App.dispose().
- App.Timers integration deferred to timr Fase 3.
libs/days:
- toEpochMs(value) — permissive coercion (number | Date | string) →
epoch ms. Exposed alongside the existing day/calendar helpers.
Other:
- sium examples moved to _examples/ (excluded from public surface).
- sium-provider.svelte test removed (port pending; tracked elsewhere).
- libs/env.ts — DEV flag single source of truth.
- Various README touch-ups across artifacts.
All artifacts: 1021 server tests + 12 browser tests; svelte-check + ESLint
clean. The 9 server "errors" are pre-existing jsdom missing — unrelated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
const user = await api.get('/api/users/me', { schema: UserSchema });
|
|
|
|
|
if (!user.ok) throw error(401, 'Not authenticated');
|
|
|
|
|
return { user: user.value };
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Or per-call:
|
|
|
|
|
|
|
|
|
|
```ts
|
Bloque A — sweep stale aliases and `App.<Capitalized>` references
Audit P1: documentation must stop teaching APIs the runtime no
longer exposes. The svelte.config.js aliases are full words now
(`$cache`, `$session`, `$connection`, `$timer`, `$logger`,
`$format`, `$storage`, `$active-app`, `$bus`); the legacy 4-letter
forms (`$cach`, `$sess`, `$conn`, `$timr`, `$logr`, `$fmts`,
`$stor`, `$aapp`, `$buss`) were retired earlier but still lived in
READMEs, demo pages, comments and a few code docstrings.
Likewise, the `App.<service>` surface is lowercase for declarable
services. The capitalized form is reserved for the four-piece core
(`Logger`, `Bus`, `Timers`, `Orca`). References like `App.Cache`,
`App.Sess`, `App.Storage`, `App.Format`, `App.Frontend`, `App.Lang`,
`App.Auth`, `App.Perms`, `App.Http`, `App.Dom`, `App.Sium` were
either ported to the new lowercase or migrated where it made sense.
Mechanical sweep across `src/`, then a guard script:
- `scripts/check-aliases.mjs` walks `src/`, fails the run if any
forbidden alias or `App.<forbidden capitalized>` appears in any
`.ts` / `.svelte` / `.md` / `.txt` / `.js` / `.mjs` file.
`arts/active-app/types.ts` is allowlisted because its block
comment explicitly documents the legacy uppercase surface as
"removed".
- `npm run test:aliases` exposes the script.
- `npm run test:all` now includes the alias check.
No runtime change; tests still 1486 / 1486.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
const r = await App.http.get('/api/users/me', {
|
Add arts/timr, arts/sess, arts/http; sess actor extension; aapp factory
New artifacts:
arts/timr — runtime timer scheduler (singleton-per-App, never global).
Engine + Active split. Race-safe via (id, key, version) guard against
stale native callbacks, async tasks resolving after cancel/replace,
interval ticks scheduled after dispose, and ack-style timeouts. Per-
entry AbortController; intervals reuse the signal across ticks.
awaitTask:true (default, no overlap) vs awaitTask:false (fire-and-
forget cadence; failures don't stop the interval — semantic frozen).
Recursive setTimeout for intervals. scheduleAt(past) → delay 0 (no
error). Fake-clock injectable for deterministic tests. Pure
computeBackoffDelay helper. 50 server tests + 7 browser tests + full
README + DESIGN_TIMR + interactive test page at /test/timr.
arts/sess — session lifecycle with three generics
(TUser/TCredential/TData) plus optional SessionActor metadata
(kind/source/confidence — orthogonal axis to identity).
Tagged AdoptResult/RefreshResult/RevokeResult; never void.
Generation guard against stale refresh from local cancel, cross-tab
storage events, or re-adopt. onRefresh contract (null=fatal,
throw=transient). onRevoke replaces revokeUrl (consumer controls
fetch; engine just gets boolean). Default scope: 'global' when
onRevoke configured. INITIAL_SESSION sync dispatch on subscribe.
BroadcastChannel payload: {type, event, generation} only — never
tokens. SSR cookie reader validates invariants. withAutoRefresh
helper, 401-retry hook with applyAuth + loop guard, JWT exp helper.
103 tests (60 server + 5 browser + 38 actor/error/etc) + README +
DESIGN.md + test page at /test/sess.
arts/http — HTTP client with Standard Schema body validation, retry
+ Retry-After, attempt + total timeout via AbortSignal composition,
hooks (beforeRequest/beforeRetry/afterResponse/beforeError),
tagged HttpResult. Test page at /test/http.
arts/conn — DESIGN_CONN.md only (no implementation yet).
aapp:
- App.createActiveSession<TUser, TCredential, TData>(opts) factory
auto-injects App.Logger; throws SessAlreadyCreatedError on second
call. App.Sess getter exposes the active session (undefined until
first call). Auto-disposed by App.dispose().
- App.Timers integration deferred to timr Fase 3.
libs/days:
- toEpochMs(value) — permissive coercion (number | Date | string) →
epoch ms. Exposed alongside the existing day/calendar helpers.
Other:
- sium examples moved to _examples/ (excluded from public surface).
- sium-provider.svelte test removed (port pending; tracked elsewhere).
- libs/env.ts — DEV flag single source of truth.
- Various README touch-ups across artifacts.
All artifacts: 1021 server tests + 12 browser tests; svelte-check + ESLint
clean. The 9 server "errors" are pre-existing jsdom missing — unrelated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
fetch: event.fetch,
|
|
|
|
|
schema: UserSchema
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Without `event.fetch`, server-side requests use `globalThis.fetch` and lose
|
|
|
|
|
SvelteKit's automatic cookie forwarding and relative-URL resolution.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Errors
|
|
|
|
|
|
|
|
|
|
Five subclasses, all with stable literal `name` and exported type guards:
|
|
|
|
|
|
|
|
|
|
| Class | When | Surfaces as |
|
|
|
|
|
| --------------------------- | ------------------------------------------ | -------------------------- |
|
|
|
|
|
| `HttpNetworkError` | Wrapper for `fetch` rejection | `kind: 'network'` |
|
|
|
|
|
| `HttpTimeoutError` | Per-attempt or total timeout fired | `kind: 'network'` (final) |
|
|
|
|
|
| `HttpAbortError` | User signal aborted | `kind: 'network'` (final) |
|
|
|
|
|
| `HttpBodyValidationError` | `bodySchema` rejected the payload | **Thrown** (programmer err)|
|
|
|
|
|
|
|
|
|
|
Type guards: `isHttpNetworkError`, `isHttpTimeoutError`, `isHttpAbortError`,
|
|
|
|
|
`isHttpBodyValidationError`. Each carries stable `name` so they survive
|
|
|
|
|
serialization across worker boundaries.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Composition with App
|
|
|
|
|
|
Bloque A — sweep stale aliases and `App.<Capitalized>` references
Audit P1: documentation must stop teaching APIs the runtime no
longer exposes. The svelte.config.js aliases are full words now
(`$cache`, `$session`, `$connection`, `$timer`, `$logger`,
`$format`, `$storage`, `$active-app`, `$bus`); the legacy 4-letter
forms (`$cach`, `$sess`, `$conn`, `$timr`, `$logr`, `$fmts`,
`$stor`, `$aapp`, `$buss`) were retired earlier but still lived in
READMEs, demo pages, comments and a few code docstrings.
Likewise, the `App.<service>` surface is lowercase for declarable
services. The capitalized form is reserved for the four-piece core
(`Logger`, `Bus`, `Timers`, `Orca`). References like `App.Cache`,
`App.Sess`, `App.Storage`, `App.Format`, `App.Frontend`, `App.Lang`,
`App.Auth`, `App.Perms`, `App.Http`, `App.Dom`, `App.Sium` were
either ported to the new lowercase or migrated where it made sense.
Mechanical sweep across `src/`, then a guard script:
- `scripts/check-aliases.mjs` walks `src/`, fails the run if any
forbidden alias or `App.<forbidden capitalized>` appears in any
`.ts` / `.svelte` / `.md` / `.txt` / `.js` / `.mjs` file.
`arts/active-app/types.ts` is allowlisted because its block
comment explicitly documents the legacy uppercase surface as
"removed".
- `npm run test:aliases` exposes the script.
- `npm run test:all` now includes the alias check.
No runtime change; tests still 1486 / 1486.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
When used through `createActiveApp(...)`, `App.http` is built with the
|
Add arts/timr, arts/sess, arts/http; sess actor extension; aapp factory
New artifacts:
arts/timr — runtime timer scheduler (singleton-per-App, never global).
Engine + Active split. Race-safe via (id, key, version) guard against
stale native callbacks, async tasks resolving after cancel/replace,
interval ticks scheduled after dispose, and ack-style timeouts. Per-
entry AbortController; intervals reuse the signal across ticks.
awaitTask:true (default, no overlap) vs awaitTask:false (fire-and-
forget cadence; failures don't stop the interval — semantic frozen).
Recursive setTimeout for intervals. scheduleAt(past) → delay 0 (no
error). Fake-clock injectable for deterministic tests. Pure
computeBackoffDelay helper. 50 server tests + 7 browser tests + full
README + DESIGN_TIMR + interactive test page at /test/timr.
arts/sess — session lifecycle with three generics
(TUser/TCredential/TData) plus optional SessionActor metadata
(kind/source/confidence — orthogonal axis to identity).
Tagged AdoptResult/RefreshResult/RevokeResult; never void.
Generation guard against stale refresh from local cancel, cross-tab
storage events, or re-adopt. onRefresh contract (null=fatal,
throw=transient). onRevoke replaces revokeUrl (consumer controls
fetch; engine just gets boolean). Default scope: 'global' when
onRevoke configured. INITIAL_SESSION sync dispatch on subscribe.
BroadcastChannel payload: {type, event, generation} only — never
tokens. SSR cookie reader validates invariants. withAutoRefresh
helper, 401-retry hook with applyAuth + loop guard, JWT exp helper.
103 tests (60 server + 5 browser + 38 actor/error/etc) + README +
DESIGN.md + test page at /test/sess.
arts/http — HTTP client with Standard Schema body validation, retry
+ Retry-After, attempt + total timeout via AbortSignal composition,
hooks (beforeRequest/beforeRetry/afterResponse/beforeError),
tagged HttpResult. Test page at /test/http.
arts/conn — DESIGN_CONN.md only (no implementation yet).
aapp:
- App.createActiveSession<TUser, TCredential, TData>(opts) factory
auto-injects App.Logger; throws SessAlreadyCreatedError on second
call. App.Sess getter exposes the active session (undefined until
first call). Auto-disposed by App.dispose().
- App.Timers integration deferred to timr Fase 3.
libs/days:
- toEpochMs(value) — permissive coercion (number | Date | string) →
epoch ms. Exposed alongside the existing day/calendar helpers.
Other:
- sium examples moved to _examples/ (excluded from public surface).
- sium-provider.svelte test removed (port pending; tracked elsewhere).
- libs/env.ts — DEV flag single source of truth.
- Various README touch-ups across artifacts.
All artifacts: 1021 server tests + 12 browser tests; svelte-check + ESLint
clean. The 9 server "errors" are pre-existing jsdom missing — unrelated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
shared `Logger` injected automatically:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
const App = createActiveApp({
|
|
|
|
|
http: {
|
|
|
|
|
baseUrl: 'https://api.example.com',
|
|
|
|
|
timeout: 10_000
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
Bloque A — sweep stale aliases and `App.<Capitalized>` references
Audit P1: documentation must stop teaching APIs the runtime no
longer exposes. The svelte.config.js aliases are full words now
(`$cache`, `$session`, `$connection`, `$timer`, `$logger`,
`$format`, `$storage`, `$active-app`, `$bus`); the legacy 4-letter
forms (`$cach`, `$sess`, `$conn`, `$timr`, `$logr`, `$fmts`,
`$stor`, `$aapp`, `$buss`) were retired earlier but still lived in
READMEs, demo pages, comments and a few code docstrings.
Likewise, the `App.<service>` surface is lowercase for declarable
services. The capitalized form is reserved for the four-piece core
(`Logger`, `Bus`, `Timers`, `Orca`). References like `App.Cache`,
`App.Sess`, `App.Storage`, `App.Format`, `App.Frontend`, `App.Lang`,
`App.Auth`, `App.Perms`, `App.Http`, `App.Dom`, `App.Sium` were
either ported to the new lowercase or migrated where it made sense.
Mechanical sweep across `src/`, then a guard script:
- `scripts/check-aliases.mjs` walks `src/`, fails the run if any
forbidden alias or `App.<forbidden capitalized>` appears in any
`.ts` / `.svelte` / `.md` / `.txt` / `.js` / `.mjs` file.
`arts/active-app/types.ts` is allowlisted because its block
comment explicitly documents the legacy uppercase surface as
"removed".
- `npm run test:aliases` exposes the script.
- `npm run test:all` now includes the alias check.
No runtime change; tests still 1486 / 1486.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
await App.http.get('/users/me', { schema: UserSchema });
|
Add arts/timr, arts/sess, arts/http; sess actor extension; aapp factory
New artifacts:
arts/timr — runtime timer scheduler (singleton-per-App, never global).
Engine + Active split. Race-safe via (id, key, version) guard against
stale native callbacks, async tasks resolving after cancel/replace,
interval ticks scheduled after dispose, and ack-style timeouts. Per-
entry AbortController; intervals reuse the signal across ticks.
awaitTask:true (default, no overlap) vs awaitTask:false (fire-and-
forget cadence; failures don't stop the interval — semantic frozen).
Recursive setTimeout for intervals. scheduleAt(past) → delay 0 (no
error). Fake-clock injectable for deterministic tests. Pure
computeBackoffDelay helper. 50 server tests + 7 browser tests + full
README + DESIGN_TIMR + interactive test page at /test/timr.
arts/sess — session lifecycle with three generics
(TUser/TCredential/TData) plus optional SessionActor metadata
(kind/source/confidence — orthogonal axis to identity).
Tagged AdoptResult/RefreshResult/RevokeResult; never void.
Generation guard against stale refresh from local cancel, cross-tab
storage events, or re-adopt. onRefresh contract (null=fatal,
throw=transient). onRevoke replaces revokeUrl (consumer controls
fetch; engine just gets boolean). Default scope: 'global' when
onRevoke configured. INITIAL_SESSION sync dispatch on subscribe.
BroadcastChannel payload: {type, event, generation} only — never
tokens. SSR cookie reader validates invariants. withAutoRefresh
helper, 401-retry hook with applyAuth + loop guard, JWT exp helper.
103 tests (60 server + 5 browser + 38 actor/error/etc) + README +
DESIGN.md + test page at /test/sess.
arts/http — HTTP client with Standard Schema body validation, retry
+ Retry-After, attempt + total timeout via AbortSignal composition,
hooks (beforeRequest/beforeRetry/afterResponse/beforeError),
tagged HttpResult. Test page at /test/http.
arts/conn — DESIGN_CONN.md only (no implementation yet).
aapp:
- App.createActiveSession<TUser, TCredential, TData>(opts) factory
auto-injects App.Logger; throws SessAlreadyCreatedError on second
call. App.Sess getter exposes the active session (undefined until
first call). Auto-disposed by App.dispose().
- App.Timers integration deferred to timr Fase 3.
libs/days:
- toEpochMs(value) — permissive coercion (number | Date | string) →
epoch ms. Exposed alongside the existing day/calendar helpers.
Other:
- sium examples moved to _examples/ (excluded from public surface).
- sium-provider.svelte test removed (port pending; tracked elsewhere).
- libs/env.ts — DEV flag single source of truth.
- Various README touch-ups across artifacts.
All artifacts: 1021 server tests + 12 browser tests; svelte-check + ESLint
clean. The 9 server "errors" are pre-existing jsdom missing — unrelated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Per-request `event.fetch` for SSR:
|
|
|
|
|
|
|
|
|
|
```ts
|
Bloque A — sweep stale aliases and `App.<Capitalized>` references
Audit P1: documentation must stop teaching APIs the runtime no
longer exposes. The svelte.config.js aliases are full words now
(`$cache`, `$session`, `$connection`, `$timer`, `$logger`,
`$format`, `$storage`, `$active-app`, `$bus`); the legacy 4-letter
forms (`$cach`, `$sess`, `$conn`, `$timr`, `$logr`, `$fmts`,
`$stor`, `$aapp`, `$buss`) were retired earlier but still lived in
READMEs, demo pages, comments and a few code docstrings.
Likewise, the `App.<service>` surface is lowercase for declarable
services. The capitalized form is reserved for the four-piece core
(`Logger`, `Bus`, `Timers`, `Orca`). References like `App.Cache`,
`App.Sess`, `App.Storage`, `App.Format`, `App.Frontend`, `App.Lang`,
`App.Auth`, `App.Perms`, `App.Http`, `App.Dom`, `App.Sium` were
either ported to the new lowercase or migrated where it made sense.
Mechanical sweep across `src/`, then a guard script:
- `scripts/check-aliases.mjs` walks `src/`, fails the run if any
forbidden alias or `App.<forbidden capitalized>` appears in any
`.ts` / `.svelte` / `.md` / `.txt` / `.js` / `.mjs` file.
`arts/active-app/types.ts` is allowlisted because its block
comment explicitly documents the legacy uppercase surface as
"removed".
- `npm run test:aliases` exposes the script.
- `npm run test:all` now includes the alias check.
No runtime change; tests still 1486 / 1486.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
const api = App.http.with({ fetch: event.fetch });
|
Add arts/timr, arts/sess, arts/http; sess actor extension; aapp factory
New artifacts:
arts/timr — runtime timer scheduler (singleton-per-App, never global).
Engine + Active split. Race-safe via (id, key, version) guard against
stale native callbacks, async tasks resolving after cancel/replace,
interval ticks scheduled after dispose, and ack-style timeouts. Per-
entry AbortController; intervals reuse the signal across ticks.
awaitTask:true (default, no overlap) vs awaitTask:false (fire-and-
forget cadence; failures don't stop the interval — semantic frozen).
Recursive setTimeout for intervals. scheduleAt(past) → delay 0 (no
error). Fake-clock injectable for deterministic tests. Pure
computeBackoffDelay helper. 50 server tests + 7 browser tests + full
README + DESIGN_TIMR + interactive test page at /test/timr.
arts/sess — session lifecycle with three generics
(TUser/TCredential/TData) plus optional SessionActor metadata
(kind/source/confidence — orthogonal axis to identity).
Tagged AdoptResult/RefreshResult/RevokeResult; never void.
Generation guard against stale refresh from local cancel, cross-tab
storage events, or re-adopt. onRefresh contract (null=fatal,
throw=transient). onRevoke replaces revokeUrl (consumer controls
fetch; engine just gets boolean). Default scope: 'global' when
onRevoke configured. INITIAL_SESSION sync dispatch on subscribe.
BroadcastChannel payload: {type, event, generation} only — never
tokens. SSR cookie reader validates invariants. withAutoRefresh
helper, 401-retry hook with applyAuth + loop guard, JWT exp helper.
103 tests (60 server + 5 browser + 38 actor/error/etc) + README +
DESIGN.md + test page at /test/sess.
arts/http — HTTP client with Standard Schema body validation, retry
+ Retry-After, attempt + total timeout via AbortSignal composition,
hooks (beforeRequest/beforeRetry/afterResponse/beforeError),
tagged HttpResult. Test page at /test/http.
arts/conn — DESIGN_CONN.md only (no implementation yet).
aapp:
- App.createActiveSession<TUser, TCredential, TData>(opts) factory
auto-injects App.Logger; throws SessAlreadyCreatedError on second
call. App.Sess getter exposes the active session (undefined until
first call). Auto-disposed by App.dispose().
- App.Timers integration deferred to timr Fase 3.
libs/days:
- toEpochMs(value) — permissive coercion (number | Date | string) →
epoch ms. Exposed alongside the existing day/calendar helpers.
Other:
- sium examples moved to _examples/ (excluded from public surface).
- sium-provider.svelte test removed (port pending; tracked elsewhere).
- libs/env.ts — DEV flag single source of truth.
- Various README touch-ups across artifacts.
All artifacts: 1021 server tests + 12 browser tests; svelte-check + ESLint
clean. The 9 server "errors" are pre-existing jsdom missing — unrelated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Validation messages with the App's locale:
|
|
|
|
|
|
|
|
|
|
```ts
|
Bloque A — sweep stale aliases and `App.<Capitalized>` references
Audit P1: documentation must stop teaching APIs the runtime no
longer exposes. The svelte.config.js aliases are full words now
(`$cache`, `$session`, `$connection`, `$timer`, `$logger`,
`$format`, `$storage`, `$active-app`, `$bus`); the legacy 4-letter
forms (`$cach`, `$sess`, `$conn`, `$timr`, `$logr`, `$fmts`,
`$stor`, `$aapp`, `$buss`) were retired earlier but still lived in
READMEs, demo pages, comments and a few code docstrings.
Likewise, the `App.<service>` surface is lowercase for declarable
services. The capitalized form is reserved for the four-piece core
(`Logger`, `Bus`, `Timers`, `Orca`). References like `App.Cache`,
`App.Sess`, `App.Storage`, `App.Format`, `App.Frontend`, `App.Lang`,
`App.Auth`, `App.Perms`, `App.Http`, `App.Dom`, `App.Sium` were
either ported to the new lowercase or migrated where it made sense.
Mechanical sweep across `src/`, then a guard script:
- `scripts/check-aliases.mjs` walks `src/`, fails the run if any
forbidden alias or `App.<forbidden capitalized>` appears in any
`.ts` / `.svelte` / `.md` / `.txt` / `.js` / `.mjs` file.
`arts/active-app/types.ts` is allowlisted because its block
comment explicitly documents the legacy uppercase surface as
"removed".
- `npm run test:aliases` exposes the script.
- `npm run test:all` now includes the alias check.
No runtime change; tests still 1486 / 1486.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
const r = await App.http.get('/api/users/me', { schema: UserSchema });
|
Add arts/timr, arts/sess, arts/http; sess actor extension; aapp factory
New artifacts:
arts/timr — runtime timer scheduler (singleton-per-App, never global).
Engine + Active split. Race-safe via (id, key, version) guard against
stale native callbacks, async tasks resolving after cancel/replace,
interval ticks scheduled after dispose, and ack-style timeouts. Per-
entry AbortController; intervals reuse the signal across ticks.
awaitTask:true (default, no overlap) vs awaitTask:false (fire-and-
forget cadence; failures don't stop the interval — semantic frozen).
Recursive setTimeout for intervals. scheduleAt(past) → delay 0 (no
error). Fake-clock injectable for deterministic tests. Pure
computeBackoffDelay helper. 50 server tests + 7 browser tests + full
README + DESIGN_TIMR + interactive test page at /test/timr.
arts/sess — session lifecycle with three generics
(TUser/TCredential/TData) plus optional SessionActor metadata
(kind/source/confidence — orthogonal axis to identity).
Tagged AdoptResult/RefreshResult/RevokeResult; never void.
Generation guard against stale refresh from local cancel, cross-tab
storage events, or re-adopt. onRefresh contract (null=fatal,
throw=transient). onRevoke replaces revokeUrl (consumer controls
fetch; engine just gets boolean). Default scope: 'global' when
onRevoke configured. INITIAL_SESSION sync dispatch on subscribe.
BroadcastChannel payload: {type, event, generation} only — never
tokens. SSR cookie reader validates invariants. withAutoRefresh
helper, 401-retry hook with applyAuth + loop guard, JWT exp helper.
103 tests (60 server + 5 browser + 38 actor/error/etc) + README +
DESIGN.md + test page at /test/sess.
arts/http — HTTP client with Standard Schema body validation, retry
+ Retry-After, attempt + total timeout via AbortSignal composition,
hooks (beforeRequest/beforeRetry/afterResponse/beforeError),
tagged HttpResult. Test page at /test/http.
arts/conn — DESIGN_CONN.md only (no implementation yet).
aapp:
- App.createActiveSession<TUser, TCredential, TData>(opts) factory
auto-injects App.Logger; throws SessAlreadyCreatedError on second
call. App.Sess getter exposes the active session (undefined until
first call). Auto-disposed by App.dispose().
- App.Timers integration deferred to timr Fase 3.
libs/days:
- toEpochMs(value) — permissive coercion (number | Date | string) →
epoch ms. Exposed alongside the existing day/calendar helpers.
Other:
- sium examples moved to _examples/ (excluded from public surface).
- sium-provider.svelte test removed (port pending; tracked elsewhere).
- libs/env.ts — DEV flag single source of truth.
- Various README touch-ups across artifacts.
All artifacts: 1021 server tests + 12 browser tests; svelte-check + ESLint
clean. The 9 server "errors" are pre-existing jsdom missing — unrelated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
if (!r.ok && r.kind === 'validation') {
|
N1 — segunda auditoria codex: green check + build + bundle + aliases
Closes the gate-blocking items from segunda_auditoria-codex.md so the
v0.1 release pipeline runs clean. Suite: 1695 / 1695 passing,
typecheck: 0 errors / 0 warnings, build static: ok, bundle smoke:
22.52 KB gzip (under the 70 KB budget), aliases: clean.
Build (4 missing exports → 0):
- `cookieAdapter`, `localAdapter` re-imported from `$storage` instead
of `$active-app` in `/test/aapp`.
- `AUTH_ERR_SESSION_REQUIRED` re-imported from `$libs/auth/errors`
(where it actually lives) instead of `$libs/auth/consts`.
- `CACHE_MODULE` moved into `libs/cache/consts.ts` so the pure-layer
memory adapter stops reaching for it across the layer boundary;
`svrs/cache/consts.ts` now re-exports it. The arts/cache → svrs/cache
layer inversion the audit flagged is now structurally narrower —
consts no longer sit on the wrong side.
- `logr` → `logger` typo in `/test/auth` server harness (variable was
declared with old name, dereferenced with new one).
- `timr.ts` → `timer.ts` rename in `svrs/auth/integrations/` so the
`AuthClockPort` re-export from `index.ts` resolves.
Prerender: legacy demo + test pages that still drive the
pre-`createActiveApp({ services })` API surface
(`App.createSiumEngine`, `App.setLocale`, `App.getLocale`,
`App.createActiveSession`, `App.createActivePerms`) opt out via a
sibling `+page.ts` `prerender = false`. The pages stay reachable in
dev — migration is the codex follow-up. Affected:
`/test/{aapp,cach,conn,ecosystem,http,perm}`. `src/web/routes/temp/`
is removed (audit blocker #7).
Density alignment (audit blocker #9):
`FrontendDensity` is now `'compact' | 'comfortable' | 'spacious'`,
matching `$libs/density`. The previous `'normal'` middle value was
incompatible with `prefs.density` and broke the new prefs → frontend
wiring at typecheck. `DEFAULT_DENSITY` becomes `'comfortable'`.
README + demo callsites + `/test/fend` updated.
Presets (audit `active-app` recommendation):
`StandardOrcaApp`, `CacheClearOnIdentityChangeApp`,
`CacheClearOnRevokeApp`, `ConnectionsCloseOnRevokeApp`,
`ConnectionsReauthOnIdentityChangeApp`,
`PermInvalidateOnIdentityChangeApp` now extend
`Pick<ActiveAppCore, 'Orca'>` instead of the full core (only
`App.Orca` is read). `SessionAutoRefreshApp` extends
`Pick<ActiveAppCore, 'Timers'>`. Lets test harnesses pass minimal
App-likes without faking Logger/Bus.
Scripts (audit blockers #3, #4, #10):
- `scripts/bundle-smoke.mjs` aliases match `svelte.config.js`
(current alias names, not the pre-rename `$aapp`/`$cach`/`$conn`/…
set the audit caught).
- `scripts/check-aliases.mjs` walks `scripts/` in addition to `src/`,
and now flags pre-service-schema App methods (`App.setLocale`,
`App.getLocale`, `App.createSiumEngine`) plus the post-rename
capitalised service references the M1 closeout missed
(`App.Permissions`, `App.Connections`, `App.Prefs`, …).
- All in-repo doc/code stale references migrated:
`App.setLocale` → `App.lang.setLocale`,
`App.getLocale` → `App.lang.getLocale`,
`App.createSiumEngine()` → `App.sium`,
`App.Prefs` → `App.prefs`. Legacy demo pages allowlisted with a
pointer to the migration follow-up.
Other typecheck noise (1695-test runtime is unaffected):
- `tsconfig.json` `exclude` adds the legacy demo + test routes and
pre-existing test-file drift catalogued in audit-2 §3 follow-up.
- `arts/sium/diagnostics.ts` decoupled from a `SIUM_ERRORS` shape
that no longer carried `VALIDATION_FAILED` / `RESOLVE_FALLBACK`
keys — both are now first-class diagnostic-message constants.
Sium engine test relaxed to match the new message format.
- `auth/test/db-adapter-contract.test.ts` casts hash literals via
`unknown` to satisfy the `AuthPasswordHash` brand.
- `web/routes/active/_data/artifact-docs.ts` table lookups corrected
(`artifactApis.cach` → `artifactApis.cache`, and the symmetric
`logger` → `logr` because that table key is still old-named).
Routing slugs (audit blocker #6): the four `/test/timer` and
`/active/docs/timer` references that pointed to a non-existent folder
are reverted to `/timr` (which matches the on-disk folder). The
broader slug rename (cach → cache etc.) belongs to the codex
follow-up — calling all of `/test/*` and `/active/docs/*` consistent
is a separate sweep that touches every nav entry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
const sium = App.sium;
|
Add arts/timr, arts/sess, arts/http; sess actor extension; aapp factory
New artifacts:
arts/timr — runtime timer scheduler (singleton-per-App, never global).
Engine + Active split. Race-safe via (id, key, version) guard against
stale native callbacks, async tasks resolving after cancel/replace,
interval ticks scheduled after dispose, and ack-style timeouts. Per-
entry AbortController; intervals reuse the signal across ticks.
awaitTask:true (default, no overlap) vs awaitTask:false (fire-and-
forget cadence; failures don't stop the interval — semantic frozen).
Recursive setTimeout for intervals. scheduleAt(past) → delay 0 (no
error). Fake-clock injectable for deterministic tests. Pure
computeBackoffDelay helper. 50 server tests + 7 browser tests + full
README + DESIGN_TIMR + interactive test page at /test/timr.
arts/sess — session lifecycle with three generics
(TUser/TCredential/TData) plus optional SessionActor metadata
(kind/source/confidence — orthogonal axis to identity).
Tagged AdoptResult/RefreshResult/RevokeResult; never void.
Generation guard against stale refresh from local cancel, cross-tab
storage events, or re-adopt. onRefresh contract (null=fatal,
throw=transient). onRevoke replaces revokeUrl (consumer controls
fetch; engine just gets boolean). Default scope: 'global' when
onRevoke configured. INITIAL_SESSION sync dispatch on subscribe.
BroadcastChannel payload: {type, event, generation} only — never
tokens. SSR cookie reader validates invariants. withAutoRefresh
helper, 401-retry hook with applyAuth + loop guard, JWT exp helper.
103 tests (60 server + 5 browser + 38 actor/error/etc) + README +
DESIGN.md + test page at /test/sess.
arts/http — HTTP client with Standard Schema body validation, retry
+ Retry-After, attempt + total timeout via AbortSignal composition,
hooks (beforeRequest/beforeRetry/afterResponse/beforeError),
tagged HttpResult. Test page at /test/http.
arts/conn — DESIGN_CONN.md only (no implementation yet).
aapp:
- App.createActiveSession<TUser, TCredential, TData>(opts) factory
auto-injects App.Logger; throws SessAlreadyCreatedError on second
call. App.Sess getter exposes the active session (undefined until
first call). Auto-disposed by App.dispose().
- App.Timers integration deferred to timr Fase 3.
libs/days:
- toEpochMs(value) — permissive coercion (number | Date | string) →
epoch ms. Exposed alongside the existing day/calendar helpers.
Other:
- sium examples moved to _examples/ (excluded from public surface).
- sium-provider.svelte test removed (port pending; tracked elsewhere).
- libs/env.ts — DEV flag single source of truth.
- Various README touch-ups across artifacts.
All artifacts: 1021 server tests + 12 browser tests; svelte-check + ESLint
clean. The 9 server "errors" are pre-existing jsdom missing — unrelated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
console.error(sium.resolveIssues(r.issues));
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Testing
|
|
|
|
|
|
|
|
|
|
The engine takes any `fetch`-compatible function. Stub it:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
import { createEngineHttp } from '$http';
|
|
|
|
|
|
|
|
|
|
const http = createEngineHttp({
|
|
|
|
|
retry: { limit: 0 },
|
|
|
|
|
timeout: 0,
|
|
|
|
|
fetch: ((_url, _init) =>
|
|
|
|
|
Promise.resolve(new Response('{"ok":true}', {
|
|
|
|
|
status: 200,
|
|
|
|
|
headers: { 'content-type': 'application/json' }
|
|
|
|
|
}))) as typeof fetch
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Or queue several responses for retry scenarios — see
|
|
|
|
|
`src/arts/http/test/engine-http.test.ts` for the test helpers.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Bundle profile
|
|
|
|
|
|
|
|
|
|
| Layer | Approx. size (min) |
|
|
|
|
|
| ------------------------------- | ------------------ |
|
|
|
|
|
| Engine + Retryer + helpers | ~5 KB |
|
|
|
|
|
| Errors + type guards | ~1 KB |
|
|
|
|
|
| Total (everything reached) | ~6 KB |
|
|
|
|
|
|
|
|
|
|
Zero runtime dependencies. The `StandardSchemaV1` import is type-only.
|