Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
eda6ad8
feat: migrate from globalstorage files to syncom
Alex-Dolid Sep 4, 2026
62565ea
feat: added pnpm
Alex-Dolid Sep 4, 2026
6e7890a
fix: bring syncom infra up to par after globalstorage migration
Alex-Dolid Sep 4, 2026
7bbe245
feat: support custom id generation via getId option
Alex-Dolid Sep 5, 2026
e2e0988
feat: replicate storage between nodes over an injected transport
Alex-Dolid Sep 5, 2026
a51df71
fix: never stamp a local write below state the node already holds
Alex-Dolid Sep 5, 2026
2ee511d
feat: inject OpenTelemetry the way wrpc does, without carrying trace …
Alex-Dolid Sep 5, 2026
bb45ced
feat: hook primitives and a contained event path
Alex-Dolid Sep 5, 2026
5a81512
fix: serialize a node's registry writes and stop saving after hang-up
Alex-Dolid Sep 5, 2026
1b72a84
feat(storage): beforeWrite/afterWrite and beforeRead/afterRead hooks
Alex-Dolid Sep 5, 2026
e3bfe53
feat(storage): beforeApply/afterApply hooks, skipped ops, apply event
Alex-Dolid Sep 5, 2026
1a06654
feat(sync): beforeConnect/beforeBootstrap hooks and lifecycle events
Alex-Dolid Sep 5, 2026
587371e
types: event maps, generic on/once/off, the SyncManager surface, a cl…
Alex-Dolid Sep 5, 2026
4d26de9
docs: hooks and events guide, reference, CLAUDE.md rules
Alex-Dolid Sep 5, 2026
da4d614
chore: add Claude skills from metaschema
Alex-Dolid Oct 18, 2026
a168ad4
feat(schema): entity definitions, injected validators, ValidationErro…
Alex-Dolid Oct 18, 2026
7857684
test(telemetry): wait for the handshake before tracing the relay push
Alex-Dolid Oct 18, 2026
59f86bf
feat(schema): the writer's version on ops and entries — v slot, meta.…
Alex-Dolid Oct 18, 2026
7967ee3
feat(schema): the lens — migrations then validate on read, migrate(),…
Alex-Dolid Oct 18, 2026
b83e7c3
refactor(contracts): move SmartContract and DataReader to @alexify/sy…
Alex-Dolid Oct 18, 2026
a7c5555
feat(backend): inject the filesystem adapter — open({ fsi }), isFsi, …
Alex-Dolid Oct 18, 2026
6c0e985
feat(idb): @alexify/syncom/idb — the storage backend over IndexedDB
Alex-Dolid Oct 18, 2026
b5b52c0
feat(storage): exclusive open — lock 'fail' | 'wait' | false
Alex-Dolid Oct 18, 2026
e51a2f5
fix(lock): name the promise parameter as the lint rule asks
Alex-Dolid Oct 18, 2026
4036ef0
feat(query): the record catalog — list() and hasRecord() without I/O,…
Alex-Dolid Oct 18, 2026
045b3e9
feat(query): Mango cursors, unique keys, watch()
Alex-Dolid Oct 18, 2026
924efaf
feat(ops): the actor on ops — open({ actor }) and a per-write { actor }
Alex-Dolid Oct 18, 2026
6a74c89
feat(sync): per-peer push limits, unit meta, the reserved shared-log …
Alex-Dolid Oct 18, 2026
7c8bbfa
feat(storage): purge — the right to be forgotten, in its minimal safe…
Alex-Dolid Oct 18, 2026
ca340ea
feat(wrpc): WebRTC through wrpc 2 — peer links as syncom links, a bro…
Alex-Dolid Oct 18, 2026
d99ae70
docs: schema, query, backends and scaling guides; index, README, keyw…
Alex-Dolid Oct 18, 2026
7d1981d
test(wrpc): the onRequest rate-limit recipe over unit({ meta })
Alex-Dolid Oct 19, 2026
9b24912
fix(security): check ids, op shape, skew and scopes at the trust boun…
Alex-Dolid Oct 20, 2026
0ca2132
fix(storage): the failure paths — sealed-after-applied log, pages tha…
Alex-Dolid Oct 20, 2026
1d1eb67
fix(sync): recover a peer left behind by a failed push, on the link's…
Alex-Dolid Oct 20, 2026
b1e779f
perf(storage): an optional chain, weak live Records, catalog saves of…
Alex-Dolid Oct 20, 2026
395834e
perf(storage): a bootstrap page as one batch; snapshots over the catalog
Alex-Dolid Oct 20, 2026
a9b4a3a
feat(telemetry): the signals an operator was missing, and the log eve…
Alex-Dolid Oct 20, 2026
ecd6512
feat(storage): stats(), hosted(), lastError, the log event, and via a…
Alex-Dolid Oct 20, 2026
fc9d6fd
docs: what the docs, the types and the comments said that the code di…
Alex-Dolid Oct 20, 2026
f25b898
refactor: the debt with no decision attached — shared halves, lock kn…
Alex-Dolid Oct 21, 2026
e7e0faa
refactor(storage): the engine as modules — the facade, three write pa…
Alex-Dolid Oct 21, 2026
9d69aa8
feat(errors): SyncomError — a code on every operational error, a reas…
Alex-Dolid Oct 21, 2026
30031ac
feat: top-k queries, repair(id), a feed cursor that moves past other …
Alex-Dolid Oct 21, 2026
4d6391e
docs: benchmarks against nine libraries, a feature matrix against eig…
Alex-Dolid Oct 21, 2026
d5d8abe
perf(catalog): save it every sixteenth of its size; version 1.0.0
Alex-Dolid Oct 21, 2026
f306f51
Update benchmarks.md
Alex-Dolid Oct 21, 2026
fed3143
feat(ops): observed-remove sets — add/remove ops, meta.sets, reset an…
Alex-Dolid Oct 21, 2026
afaffbe
feat(storage): add() and remove() — sets through the three write paths
Alex-Dolid Oct 21, 2026
977e93b
feat(types): add/remove, meta.sets, FieldKind and SetRemoval on the t…
Alex-Dolid Oct 21, 2026
e2aa61e
fix(catalog): post an array under each element, so an indexed array n…
Alex-Dolid Oct 21, 2026
c533c02
docs: sets in the guides, the reference, the matrix and the rules; ap…
Alex-Dolid Oct 21, 2026
b832bca
feat(crypto): Ed25519 signing keys — sign and verify on both halves
Alex-Dolid Oct 21, 2026
fd37d62
feat(storage): the node id is the Ed25519 public key
Alex-Dolid Oct 21, 2026
66f9605
feat(ops): the canonical signed message, and three more packed slots
Alex-Dolid Oct 21, 2026
36405f7
feat(storage): every op this node mints is signed
Alex-Dolid Oct 21, 2026
94b6984
feat(sync): verify a peer's signatures before admission; sync.signatures
Alex-Dolid Oct 21, 2026
a991e84
feat(hosting): announce refused signatures; a 'require' host refuses …
Alex-Dolid Oct 21, 2026
f05383f
docs: signed ops — the Sync guide's Signatures section, the threat mo…
Alex-Dolid Oct 21, 2026
5102ad5
feat(catalog): the search seam — entity search fields, the TextIndex …
Alex-Dolid Oct 21, 2026
9d4195e
feat(search): @alexify/syncom/search — the trigram index behind $text
Alex-Dolid Oct 22, 2026
3087bc7
test(search): the suite over both halves
Alex-Dolid Oct 22, 2026
6349d23
bench(search): the index alone, and $text through the storage
Alex-Dolid Oct 22, 2026
2ff88d5
docs(search): full-text search in the query guide, the reference and …
Alex-Dolid Oct 22, 2026
965abd8
perf(sync): verify a peer's signatures on Node's thread pool
Alex-Dolid Oct 22, 2026
bef2ff2
chore: version 0.0.1; docs brought up to date, benchmarks re-measured
Alex-Dolid Oct 22, 2026
7ac7054
test(mesh): settle() waits for the mutation queues, not the vectors a…
Alex-Dolid Oct 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
515 changes: 515 additions & 0 deletions .claude/skills/data-structures/SKILL.md

Large diffs are not rendered by default.

127 changes: 127 additions & 0 deletions .claude/skills/error-handling/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
---
name: error-handling
description: Apply error handling and recovery patterns in JavaScript/TypeScript or Node.js. Use when implementing error handling, retry logic, or when the user mentions domain errors, error recovery, error escalation.
---

# Error Handling

## Error classification

- Programming errors: Bugs (TypeError, ReferenceError, assertion failures, etc.). Fix the code; do not catch and continue
- Operational errors: Expected failures (network timeout, file not found, invalid input, etc.). Handle gracefully with recovery, escalation, user notification, or logging

## Sync error handling

```javascript
try {
const result = parse(input);
return result;
} catch (error) {
console.error({ error });
return defaultValue;
}
```

## Async error handling

```javascript
try {
const data = await fetchData(url);
return data;
} catch (error) {
if (error.code === 'ECONNREFUSED') return fallback();
throw err;
}
```

## DomainError

Structured business errors with codes for the API layer:

```javascript
({
method: async ({ email }) => {
const user = await domain.user.findByEmail(email);
if (!user) return new DomainError('ENOTFOUND');
return user;
},
errors: { ENOTFOUND: 'User not found' },
});
```

Domain layer throws plain errors; API layer translates to DomainError when needed.

## Error propagation across layers

```
domain throws Error → API catches → returns DomainError(code) → client gets structured error
```

Do not expose internal error details to clients; map to known error codes or general errors.

## Conventions

- Distinguish programmer vs operational errors; only recover from operational
- Use DomainError for business validation in API methods; throw for programming bugs
- Never swallow errors silently; always log or propagate
- Implement graceful shutdown: stop accepting connections, drain existing, release resources
- Use retry with exponential backoff for transient failures (network, DB connections)
- Always handle both `uncaughtException` and `unhandledRejection` at process level

## Retry pattern

```javascript
const retry = async (fn, { attempts = 3, delay = 1000 } = {}) => {
for (let i = 0; i < attempts; i++) {
try {
return await fn();
} catch (err) {
if (i === attempts - 1) throw err;
await new Promise((r) => setTimeout(r, delay * (i + 1)));
}
}
};
```

## Process-level handlers

```javascript
process.on('uncaughtException', (error) => {
console.error('Uncaught:', error);
process.exit(1);
});

process.on('unhandledRejection', (reason) => {
console.error('Unhandled rejection:', reason);
});
```

Log and exit on uncaughtException (state may be corrupted). Log unhandledRejection and consider it a bug to fix.

## Graceful shutdown

Track connections and drain before exit:

```javascript
const connections = new Map();

server.on('connection', (conn) => {
const res = null;
connections.set(conn, res);
conn.on('close', () => connections.delete(conn));
});

const shutdown = () => {
server.close(() => {
freeResources();
process.exit(0);
});
for (const [conn, res] of connections) {
if (res) res.end('Server shutting down');
conn.destroy();
}
};

process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);
```
109 changes: 109 additions & 0 deletions .claude/skills/js-conventions/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
---
name: js-conventions
description: Apply the Alexis JavaScript style (Metarhia-derived, linted by oxlint and formatted by oxfmt). Use when writing or editing .js, .mjs, .ts files (with certain corrections for typescript), formatting code, or when the user asks about code style or linting.
---

<!-- Adapted from metaskills v1.0.5 (skills/js-conventions) for the Alexis toolchain and for syncom:
oxlint + oxfmt instead of ESLint + Prettier, pnpm instead of npm, 120 columns instead of 80,
'use strict' in every src file (metaschema omits it), and comments that explain why.
The syncom-specific rules (op model, browser halves, hooks, no runtime dependencies) live in
CLAUDE.md, which wins if the two ever disagree. -->

# JavaScript Code Style

Use following conventions for JavaScript and TypeScript code.

## Preparations

- Linting is oxlint (`.oxlintrc.json`) and formatting is oxfmt (`.oxfmtrc.json`, `.editorconfig`);
never add ESLint or Prettier
- The package manager is pnpm; the scripts are already in package.json:
- `pnpm lint`: oxlint over `src`, `tests`, `scripts` and `bench`
- `pnpm format`: oxfmt rewrites those files in place
- `pnpm format:check`: oxfmt check only, as CI runs it
- Before and after code analysis with AI run `pnpm lint` and `pnpm test`
- Use `pnpm format` to fix formatting; fix lint errors in the code, and suppress a rule only for an
intentional construct with a targeted `// oxlint-disable-next-line <rule>`

## Formatting

- Use single quotes and semicolons
- Keep max line length 120 (ignore URLs)
- Use trailing commas in multiline arrays, objects, and params
- Keep one empty line between semantic blocks
- Follow oxfmt and `.editorconfig` for spacing, braces, and indentation (2 spaces, LF)

## Modules

- Use CommonJS: `require` and `module.exports`, no `import`/`export` in `.js` sources; there is no
build step, `src/` is what gets published
- Start every `src/` file with `'use strict'`
- Use the `node:` prefix for Node.js built-in modules (`require('node:fs')`)
- Always include the `.js` extension in relative requires (`require('./ops.js')`)
- No runtime dependencies: the only bare `require` allowed in `src/` is a `node:` built-in, and
none at all in a file that reaches the browser bundle (put helpers in `src/utils.js`)
- A platform split is a `*.browser.js` file (dot, not dash); see CLAUDE.md for the three edits it needs

## Naming

- Use `camelCase` for variables and parameters
- Use `UpperCamelCase` for classes and types
- Use `UPPER_SNAKE_CASE` for constants
- Use `new` with capitalized constructor names
- Use `error` in catch blocks, not `err`
- Use short names (`e`, `i`) only in short one-line callbacks
- Use descriptive names in multiline callbacks (`event`, `index`, `item`)
- Use boolean prefixes: `is`, `has`, `can`, `should`
- Use singular names for entities and plural names for collections
- Use verb names for functions and handlers (`parse`, `create`, `handle`, `on`)
- Include units in numeric names (`timeoutMs`, `sizeBytes`) except options

## Best practices

- Prefer `const`, minimize `let`, never use `var`
- Prefer arrow functions (except prototype/class methods)
- Do not mutate input parameters; avoid `arguments`
- Keep functions small and single-purpose
- Decompose long expressions into intermediate variables
- Prefer explicit loops (`for`, `for..of`); index loops in hot paths; avoid `forEach` and `reduce`
(the code base has none); `map`, `filter` and `find` are fine for a one-shot transform off the hot path
- Use `===` and `!==`; the one loose comparison is `value == null` for "null or undefined"
(`eqeqeq` ignores `null`)
- Keep return types consistent
- Avoid nested ternaries and deep callback nesting
- Self-documented and self-descriptive code; a comment explains why (an invariant, a trap, a
decision), never what the next line does
- Throw a `TypeError` for a bad argument, prefixed with the thing being configured
(`${label}: hooks must be an object`); throw a typed error with a stable `code` when a caller
has to branch on it (`HookError`, `code: 'HOOK_FAILED'`, with the original as `cause`); never
swallow an error without saying why in a comment
- Events leave through `notify()` and never fail the operation they observe; a hook may fail it
- `await` `calculateHash`, `encrypt`, `decrypt`, `sign` and `verify` in shared code: they are synchronous in Node and
promise-returning in the browser

## Optimizations

- Keep object shapes stable: same keys, types, and key order
- Change shape only in constructors and creational patterns; avoid mix-ins
- Use shape mutations only for metaprogramming
- Use `null` for empty reference types; use `undefined` for empty primitives
- Avoid array destructuring in assignments and loops; object destructuring is ok
- Keep hot functions monomorphic: stable arg count, types, and return types
- Avoid reading the same property from many unrelated object shapes
- Prefer string or number literals for status/codes; avoid mixing primitives with objects in one variable
- Keep objects that share a property to the same shape; avoid polymorphic hotspots
- Prefer fixed property access (`obj.x`) over dynamic keys (`obj[key]`)
- Keep arrays dense, avoid holes and mixed element kinds
- Initialize object fields in constructor/factory once and in one order
- Set field to `null` or `undefined`; avoid `delete`
- Move reusable callbacks outside loops
- Keep `try/catch`, spread/rest away from hot loops
- Use `Map` for dynamic key dictionaries, plain objects for fixed schemas
- Use `Set` for big and dynamic collections, prefer arrays for short and stable
- Use `Object.create(null)` for pure dictionaries
- Use typed arrays for numeric and binary workloads
- Reduce GC pressure: reuse arrays, objects, and buffers when safe
- In the hot path (applying and merging ops, `OpLog.append`, vector clocks, `packOps`): no closure
or `RegExp` created per call, one object shape per kind of op and log entry, and `try/catch`
only around user code (hooks, listeners) and I/O
- Check `pnpm bench` before and after touching the hot path; a number moved is a finding
Loading
Loading