Pilot of phase 4 plus a libs/errs API change introduced after the
user pointed out that `code('buss::disposed')` repeats the module
name redundantly across every declaration.
libs/errs additions:
- New `ModuleSeed` branded type — string of the form `'<module>::'`
produced once per module.
- `moduleSeed(module)` factory: validates the module name and returns
a `ModuleSeed`.
- `errCode(parent, segment)` builder: composes a child `ErrCode` from
a `ModuleSeed` (uses `::` separator) or another `ErrCode` (uses `.`
separator). The builder picks the right separator automatically.
- `isModuleSeed(value)` discriminator.
- `matches(err, family)` now accepts `ModuleSeed | ErrCode`. Passing
a seed matches any error from that module; passing a code matches
hierarchically within the same module. Replaces the
`matchesModule(err, 'buss')` helper introduced earlier in this
session — that helper was redundant once seeds entered the API.
Sium pilot:
- arts/sium/consts.ts: declares `SIUM_ERR = moduleSeed('sium')` plus
the two child codes via `errCode(SIUM_ERR, 'validation')` and
`errCode(SIUM_ERR, 'async_schema')`. Module name appears exactly
once.
- arts/sium/core/types.ts: `SiumValidationError` and
`SiumAsyncSchemaError` extend `CodeError`. Hardcoded
`this.name = 'SiumValidationError'` literals removed. Subclass-
specific fields (`issues`, `schemaKind`) preserved.
- Adds `isSiumValidationError` / `isSiumAsyncSchemaError` guards
(closes audit-1-5 section 3.2 for sium).
- 12 tests in arts/sium/test/errors.test.ts cover subclass shape,
identity, `matches` against seed and exact code, and the fluent
decorator chain.
docs/conventions.md updated:
- Section 4 rewritten around the `moduleSeed`/`errCode` pattern.
- "Family root" wording removed (was misleading — `BUSS_ERR='buss::base'`
was a sibling, not an ancestor of its module's codes).
- `matches` documented as accepting either a seed or an ErrCode.
Verification: svelte-check 1403 files / 0 errors. Server 1315 tests
(+10 from previous), client 19 tests — all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
master
parent
38160565b2
commit
a849d00bff
@ -0,0 +1,87 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
isSiumAsyncSchemaError,
|
||||
isSiumValidationError,
|
||||
SiumAsyncSchemaError,
|
||||
SiumValidationError,
|
||||
type Issue
|
||||
} from '../index.ts';
|
||||
import {
|
||||
SIUM_ERR,
|
||||
SIUM_ERR_ASYNC_SCHEMA,
|
||||
SIUM_ERR_VALIDATION
|
||||
} from '../consts.ts';
|
||||
import { CodeError, isCodeError, matches } from '$libs/errs';
|
||||
|
||||
const ISSUE: Issue = {
|
||||
path: ['name'],
|
||||
message: 'required',
|
||||
code: 'invalid_type'
|
||||
} as Issue;
|
||||
|
||||
describe('SiumValidationError', () => {
|
||||
it('extends CodeError with the validation code', () => {
|
||||
const err = new SiumValidationError([ISSUE]);
|
||||
expect(err).toBeInstanceOf(CodeError);
|
||||
expect(err.code).toBe(SIUM_ERR_VALIDATION);
|
||||
expect(err.name).toBe(SIUM_ERR_VALIDATION);
|
||||
});
|
||||
|
||||
it('carries issues alongside the code', () => {
|
||||
const err = new SiumValidationError([ISSUE]);
|
||||
expect(err.issues).toEqual([ISSUE]);
|
||||
});
|
||||
|
||||
it('is detected by isSiumValidationError and isCodeError', () => {
|
||||
const err = new SiumValidationError([ISSUE]);
|
||||
expect(isSiumValidationError(err)).toBe(true);
|
||||
expect(isCodeError(err)).toBe(true);
|
||||
});
|
||||
|
||||
it('is matched by the SIUM_ERR module seed', () => {
|
||||
const err = new SiumValidationError([ISSUE]);
|
||||
expect(matches(err, SIUM_ERR)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SiumAsyncSchemaError', () => {
|
||||
it('extends CodeError with the async-schema code', () => {
|
||||
const err = new SiumAsyncSchemaError('object');
|
||||
expect(err).toBeInstanceOf(CodeError);
|
||||
expect(err.code).toBe(SIUM_ERR_ASYNC_SCHEMA);
|
||||
expect(err.name).toBe(SIUM_ERR_ASYNC_SCHEMA);
|
||||
});
|
||||
|
||||
it('carries the schema kind', () => {
|
||||
const err = new SiumAsyncSchemaError('union');
|
||||
expect(err.schemaKind).toBe('union');
|
||||
});
|
||||
|
||||
it('is detected by isSiumAsyncSchemaError and isCodeError', () => {
|
||||
const err = new SiumAsyncSchemaError();
|
||||
expect(isSiumAsyncSchemaError(err)).toBe(true);
|
||||
expect(isCodeError(err)).toBe(true);
|
||||
});
|
||||
|
||||
it('does not match the validation code', () => {
|
||||
const err = new SiumAsyncSchemaError();
|
||||
expect(matches(err, SIUM_ERR_VALIDATION)).toBe(false);
|
||||
expect(matches(err, SIUM_ERR_ASYNC_SCHEMA)).toBe(true);
|
||||
});
|
||||
|
||||
it('is matched by the SIUM_ERR module seed', () => {
|
||||
const err = new SiumAsyncSchemaError();
|
||||
expect(matches(err, SIUM_ERR)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CodeError fluent decorators on sium errors', () => {
|
||||
it('preserves the subclass-specific fields after withMessage', () => {
|
||||
const err = new SiumValidationError([ISSUE]);
|
||||
const decorated = err.withMessage('User-facing failure');
|
||||
expect(decorated.userMessage).toBe('User-facing failure');
|
||||
// Decoration returns plain CodeError; subclass fields stay on the
|
||||
// original instance only.
|
||||
expect(matches(decorated, SIUM_ERR_VALIDATION)).toBe(true);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in new issue