Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 0 additions & 16 deletions .eslintrc.js

This file was deleted.

25 changes: 25 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: CI

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
check:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: 1.2.23
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 22.22.0
registry-url: https://registry.npmjs.org
- run: bun install --frozen-lockfile
- run: bun run check
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
node_modules/
miakapiCredentials.json
dist/
.contract-dist/
.contract/
coverage/
1 change: 0 additions & 1 deletion .npmignore

This file was deleted.

15 changes: 15 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
ISC License

Copyright (c) 2026 Mathieu Colmon

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
203 changes: 203 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
# MiakAPI

MiakAPI is the typed Node.js SDK for running a trusted Miakapp coordinator. A
coordinator owns complete state, access, event, and function declarations for one
integration and exchanges canonical MessagePack frames with the Miakapp relay.

Version 4 is a complete replacement for the legacy callback-based MiakAPI 3
client. It is currently an alpha while the Miakapp 3.5 relay is being deployed.

## Requirements

- Node.js 22.9 or newer
- An application backend able to issue short-lived coordinator access tokens
- A Miakapp relay implementing wire protocol 1.0

MiakAPI is server-side software. Do not ship coordinator credentials, Home Keys,
or access-token providers to a browser or an untrusted plugin runtime.

## Installation

```sh
npm install miakapi@next
```

Alpha releases use the `next` npm tag. The package is ESM-only.

## Quick start

```ts
import {
ApplicationCallError,
EventDirection,
createCoordinator,
} from 'miakapi';

const coordinator = createCoordinator({
name: 'home-assistant',
accessTokenProvider: {
async getAccessToken({ coordinatorName, reason, signal }) {
const response = await fetch('https://example.test/miakapp/token', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ coordinatorName, reason }),
signal,
});
if (!response.ok) throw new Error('Access token request failed');
return response.json();
},
},
});

coordinator.configure({
state: {
'climate.living_room.temperature': 20,
},
stateAccess: [{
userId: 'user-id',
patterns: ['climate.living_room.*'],
}],
events: [{
topic: 'climate.living_room.changed',
directions:
EventDirection.acceptFromUsers |
EventDirection.publishToUsers,
}],
eventAccess: [{
userId: 'user-id',
publish: ['climate.living_room.changed'],
subscribe: ['climate.living_room.changed'],
}],
functions: {
async 'climate.living_room.set_target'(call) {
if (typeof call.arguments !== 'number') {
throw new ApplicationCallError(2001, 'Target must be a number');
}
await call.emit({ phase: 'applying' });
return { accepted: true, target: call.arguments };
},
},
});

coordinator.subscribe(({ current, reason }) => {
console.log('Miakapp coordinator status:', current, reason?.kind);
});

const session = await coordinator.start();
console.log('Ready in generation', session.generation);
```

`configure` supplies all five declaration slices as one desired snapshot. The
coordinator becomes `ready` only after the relay acknowledges them in order. A
later declaration call replaces its complete slice and temporarily returns the
coordinator to `synchronizing` until atomic activation.

## State, events, and calls

State mutations are atomic and use acknowledged string paths:

```ts
await coordinator.state.set([
{ path: 'climate.living_room.temperature', value: 21.5 },
]);
```

Event publication returns a synchronous opaque ID and a transport-handoff
promise. `sent` is not a delivery receipt; a later correlated relay rejection is
reported through `coordinator.errors`.

```ts
const event = coordinator.events.publish(
'climate.living_room.changed',
{ temperature: 21.5 },
);
await event.sent;

coordinator.errors.subscribe((failure) => {
if (failure.correlation?.localId === event.localId) {
console.error('The relay rejected the event:', failure.kind);
}
});
```

Outgoing calls expose acceptance, pull-bounded progress, and one terminal
result:

```ts
const call = coordinator.calls.start({
function: 'lighting.scene.activate',
arguments: { scene: 'evening' },
timeoutMs: 10_000,
idempotencyKey: 'intent-018f',
});

await call.accepted;
for await (const progress of call.stream) console.log(progress);
const result = await call.result;
```

MiakAPI never retries state mutations, events, or calls. An idempotency key is
passed to the callee but does not enable hidden retries.

## Failure outcomes

Every `CoordinatorFailure` includes an `outcome`:

- `not_dispatched`: local validation, offline gating, or an explicit relay
terminal proves the operation did not dispatch.
- `sent`: an event frame reached the active transport; delivery is not implied.
- `accepted`: a call was accepted before its terminal failure.
- `applied`: a state mutation was acknowledged by the relay.
- `outcome_unknown`: transport loss, deadline, or post-accept cancellation means
an external effect may already have happened.

Treat `outcome_unknown` as uncertainty, never as rollback. MiakAPI does not turn
an uncertain physical effect into a safe automatic retry.

## Lifecycle and cleanup

`start()` may be called once. `stop()` is idempotent and repeated calls return the
same terminal promise. It aborts token and handler work, settles pending
operations conservatively, removes listeners, and closes the owned socket.

```ts
await coordinator.stop({ deadlineMs: 5_000 });
```

`deadlineMs` bounds cleanup even when an injected dependency ignores its abort
signal.

## Migration from MiakAPI 3

MiakAPI 4 removes the legacy `Miakapi(home, id, secret)` constructor, Firestore
lookup, mutable `home.variables`, UI callbacks, and notification helpers. Those
APIs depended on the retired Miakapp 3 transport and are not emulated.

Integrations now:

1. obtain short-lived access material through an `AccessTokenProvider`;
2. declare complete state, ACL, event, and function slices;
3. wait for `start()` readiness before issuing effects;
4. handle uncertainty explicitly through typed failures.

For UI automation and agent-driven homes, use [miakapp.com](https://miakapp.com/)
instead of building against the retired page-callback protocol.

## Protocol and conformance

The public TypeScript API and wire codec are pinned to the Miakapp-V3 coordinator
contract at an immutable commit in [`contracts/miakapp-v3.json`](contracts/miakapp-v3.json).
The external conformance subject runs the real SDK against a deterministic relay
and must pass every scenario in the `sdk` profile.

```sh
bun install --frozen-lockfile
bun run check
```

The check includes strict type checking, unit and adversarial tests, a Node.js
package smoke test, canonical external conformance, and an npm package dry run.

## License

[ISC](LICENSE)
79 changes: 79 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions contracts/miakapp-v3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"repository": "https://github.com/Miakapp/Miakapp-V3.git",
"commit": "b927789691dd8cdebc91b673853cdc6711fe057d",
"profile": "sdk",
"schema": "miakapp.coordinator-contract/1"
}
Loading
Loading