autobonk 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +249 -0
- package/index.js +3 -0
- package/package.json +62 -0
- package/src/context.js +962 -0
- package/src/extend.js +240 -0
- package/src/manager.js +243 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Built-in blind-peering integration (opt-in via Manager/Context options).
|
|
10
|
+
|
|
11
|
+
## [1.0.0] - 2026-01-15
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Initial release.
|
package/README.md
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# Autobonk (EXPERIMENTAL)
|
|
4
|
+
|
|
5
|
+
Autobonk is a ready-made manager and context runtime for building Hypercore-powered, peer-to-peer applications with deterministic schemas and role-based permissions.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Manager orchestrates _context_ creation, join, and lifecycle management on top of Corestore.
|
|
10
|
+
- Context base class wires Autobase, Hyperbee views, and the built-in permission system.
|
|
11
|
+
- Schema extension helpers (`extendSchema`, `extendDb`, `extendDispatch`) let projects compose additional tables or routes.
|
|
12
|
+
- Pairing flow handles invite-based onboarding with encryption and optional bootstrap peers.
|
|
13
|
+
- Optional blind-peering integration keeps contexts available via configured blind peers.
|
|
14
|
+
- Subclasses get lifecycle hooks for provisioning and tearing down auxiliary resources like Hyperblobs.
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npm install autobonk
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
import { Manager, Context } from 'autobonk'
|
|
24
|
+
import * as dispatch from './spec/dispatch/index.js'
|
|
25
|
+
import db from './spec/db/index.js'
|
|
26
|
+
|
|
27
|
+
export class Room extends Context {
|
|
28
|
+
setupRoutes() {
|
|
29
|
+
this.router.add('@room/send-message', async (data, context) => {
|
|
30
|
+
const last = await this.lastMessage()
|
|
31
|
+
const index = last ? last.index + 1 : 1
|
|
32
|
+
|
|
33
|
+
await context.view.insert('@room/messages', {
|
|
34
|
+
index,
|
|
35
|
+
...data
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async allMessages() {
|
|
41
|
+
const messages = await this.base.view.find('@room/messages').toArray()
|
|
42
|
+
return messages
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async sendMessage(text) {
|
|
46
|
+
await this.base.append(this.schema.dispatch.encode('@room/send-message', { text }))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async lastMessage() {
|
|
50
|
+
return await this.base.view.findOne('@room/messages', {
|
|
51
|
+
reverse: true,
|
|
52
|
+
limit: 1
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const manager = new Manager('/tmp/autobonk-dev', {
|
|
58
|
+
ContextClass: Room,
|
|
59
|
+
schema: { db, dispatch }
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
await manager.ready()
|
|
63
|
+
const room = await manager.createContext({ name: 'Dev Room' })
|
|
64
|
+
console.log(room.key.toString('hex'))
|
|
65
|
+
await manager.close()
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Examples
|
|
69
|
+
|
|
70
|
+
- `example/basic/` – Chat room demo with a CLI (`node example/basic/cli.js`) showcasing create/join/list/connect commands.
|
|
71
|
+
- `example/forum/` – Moderated forum sample extending `Context` with role management routes; rebuild specs with the same schema script.
|
|
72
|
+
|
|
73
|
+
## API
|
|
74
|
+
|
|
75
|
+
### Manager
|
|
76
|
+
|
|
77
|
+
Manager extends `ReadyResource`; call `await manager.ready()` before first use and `await manager.close()` when you are finished.
|
|
78
|
+
|
|
79
|
+
#### `const manager = new Manager(baseDir, opts)`
|
|
80
|
+
|
|
81
|
+
Instantiate a manager rooted at `baseDir`. Provide the context constructor and schema bundle.
|
|
82
|
+
|
|
83
|
+
`opts` takes the following options:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
{
|
|
87
|
+
ContextClass, // required subclass of Context
|
|
88
|
+
schema, // required { db, dispatch }
|
|
89
|
+
bootstrap, // optional Hyperswarm bootstrap peers
|
|
90
|
+
blindPeering // optional blind-peering options or instance
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
#### `await manager.ready()`
|
|
95
|
+
|
|
96
|
+
Wait for the manager to open its corestore and metadata database. Required before invoking other methods.
|
|
97
|
+
|
|
98
|
+
#### `await manager.close()`
|
|
99
|
+
|
|
100
|
+
Close all open contexts, the local database, and the underlying corestore.
|
|
101
|
+
|
|
102
|
+
#### `const context = await manager.createContext([options])`
|
|
103
|
+
|
|
104
|
+
Provision a new context namespace, persist its metadata locally, and return an initialized `Context` instance. Accepts an optional `{ name }` label.
|
|
105
|
+
|
|
106
|
+
#### `const context = await manager.joinContext(invite, [options])`
|
|
107
|
+
|
|
108
|
+
Join an existing context using an invite string. Persists the context locally, recreates it under a deterministic namespace, and returns the initialized `Context`. Accepts an optional `{ name }` label for local metadata.
|
|
109
|
+
|
|
110
|
+
#### `const context = await manager.getContext(keyHex)`
|
|
111
|
+
|
|
112
|
+
Resolve a previously known context by its hex-encoded key. Returns a cached `Context`, lazily loads it from disk when needed, or resolves to `null` when the key has no metadata.
|
|
113
|
+
|
|
114
|
+
#### `const records = await manager.listContexts()`
|
|
115
|
+
|
|
116
|
+
Return stored context records sorted newest-first. Each record includes `key`, `encryptionKey`, `name`, `createdAt`, `isCreator`, and `namespace`.
|
|
117
|
+
|
|
118
|
+
#### `const removed = await manager.removeContext(keyHex)`
|
|
119
|
+
|
|
120
|
+
Remove cached metadata for the given context key. Closes any active instance and resolves `true` when a record was deleted, otherwise `false`.
|
|
121
|
+
|
|
122
|
+
### Context
|
|
123
|
+
|
|
124
|
+
Context extends `ReadyResource`; call `await context.ready()` before interacting and `await context.close()` to release swarm resources.
|
|
125
|
+
|
|
126
|
+
#### `const context = new Context(store, opts)`
|
|
127
|
+
|
|
128
|
+
Construct a context around a corestore namespace. Projects typically instantiate subclasses that add routes during `setupRoutes`.
|
|
129
|
+
|
|
130
|
+
`opts` takes the following options:
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
{
|
|
134
|
+
schema, // required { db, dispatch }
|
|
135
|
+
key, // optional existing context key buffer
|
|
136
|
+
encryptionKey, // optional symmetric encryption key buffer
|
|
137
|
+
bootstrap, // optional Hyperswarm bootstrap peers
|
|
138
|
+
swarm, // optional Hyperswarm instance to reuse
|
|
139
|
+
blindPeering, // optional blind-peering options or instance
|
|
140
|
+
autobase // optional additional Autobase constructor options
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
#### `await context.ready()`
|
|
145
|
+
|
|
146
|
+
Open the underlying Autobase, initialize the permission seed, and join the replication swarm if necessary.
|
|
147
|
+
|
|
148
|
+
#### `await context.close()`
|
|
149
|
+
|
|
150
|
+
Stop the pairing helpers, destroy the swarm, and close the Autobase.
|
|
151
|
+
|
|
152
|
+
#### Blind peering
|
|
153
|
+
|
|
154
|
+
Provide `blindPeering` options to `Manager` or `Context` to mirror Autobase cores to blind peers. The object is passed to `new BlindPeering(swarm, store, opts)` and may include an `autobase` key for `addAutobaseBackground` options. You may also pass a pre-built BlindPeering instance.
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
{
|
|
158
|
+
mirrors: ['...'],
|
|
159
|
+
autobase: { pick: 2 }
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
##### Subclass lifecycle hooks
|
|
164
|
+
|
|
165
|
+
Override `async setupResources()` in a subclass when you need to spin up auxiliary stores (for example Hyperblobs or servers) once the Autobase view is ready. Pair it with `async teardownResources()` to clean everything up.
|
|
166
|
+
|
|
167
|
+
#### `context.writable`
|
|
168
|
+
|
|
169
|
+
Boolean getter indicating whether the local writer currently has append privileges.
|
|
170
|
+
|
|
171
|
+
#### `context.key`
|
|
172
|
+
|
|
173
|
+
Getter returning the shared context key as a `Buffer`.
|
|
174
|
+
|
|
175
|
+
#### `context.discoveryKey`
|
|
176
|
+
|
|
177
|
+
Getter for the discovery key used to join the replication swarm.
|
|
178
|
+
|
|
179
|
+
#### `context.writerKey`
|
|
180
|
+
|
|
181
|
+
Getter for the local writer key (`Buffer`). Unique per participant.
|
|
182
|
+
|
|
183
|
+
#### `context.contextKey`
|
|
184
|
+
|
|
185
|
+
Alias for `context.key` to help distinguish shared keys from writer keys in client code.
|
|
186
|
+
|
|
187
|
+
#### `context.encryptionKey`
|
|
188
|
+
|
|
189
|
+
Getter returning the symmetric encryption key (`Buffer`).
|
|
190
|
+
|
|
191
|
+
#### `const pairer = Context.pair(store, invite, [options])`
|
|
192
|
+
|
|
193
|
+
Static helper that returns a `ContextPairer` during invite-based joins. Resolves to a writable `Context` once pairing completes.
|
|
194
|
+
|
|
195
|
+
#### `const unsubscribe = context.subscribe(listener)`
|
|
196
|
+
|
|
197
|
+
Register a callback for `update` events. Returns a function that removes the listener.
|
|
198
|
+
|
|
199
|
+
#### `const hasAccess = await context.hasPermission(subjectKey, permission[, blockIndex])`
|
|
200
|
+
|
|
201
|
+
Check whether `subjectKey` holds `permission`. Accepts an optional historical `blockIndex` for time-travel checks.
|
|
202
|
+
|
|
203
|
+
#### `await context.requirePermission(subjectKey, permission)`
|
|
204
|
+
|
|
205
|
+
Throw a `PermissionError` unless `subjectKey` currently holds `permission`. Automatically skips enforcement before the context initialization record lands.
|
|
206
|
+
|
|
207
|
+
#### `await context.defineRole(name, permissions[, actorKey][, options])`
|
|
208
|
+
|
|
209
|
+
Append or apply a role definition. Requires the actor to hold `role:create`. Accepts either a single permission string or an array.
|
|
210
|
+
|
|
211
|
+
#### `await context.grantRoles(subjectKey, roles[, actorKey][, options])`
|
|
212
|
+
|
|
213
|
+
Assign one or more role names to `subjectKey`. Requires `role:assign` and validates that every role exists before committing.
|
|
214
|
+
|
|
215
|
+
#### `await context.revokeRoles(subjectKey)`
|
|
216
|
+
|
|
217
|
+
Clear all roles for `subjectKey`. Requires `role:revoke`.
|
|
218
|
+
|
|
219
|
+
#### `const changed = await context.denounceRole(roleName[, actorKey])`
|
|
220
|
+
|
|
221
|
+
Allow an actor to drop one of their roles. Requires `role:revoke` and returns `true` when a change was written.
|
|
222
|
+
|
|
223
|
+
#### `const role = await context.getRole(name)`
|
|
224
|
+
|
|
225
|
+
Fetch a stored role definition from the view.
|
|
226
|
+
|
|
227
|
+
#### `const roles = await context.getRoles(subjectKey)`
|
|
228
|
+
|
|
229
|
+
List the role names currently attached to `subjectKey`.
|
|
230
|
+
|
|
231
|
+
#### `await context.addWriter(key[, meta])`
|
|
232
|
+
|
|
233
|
+
Append a dispatch that authorizes the provided writer key. Accepts either a `Buffer` or `Uint8Array`. Pass an optional `meta` object—currently `{ isIndexer?: boolean }`—to flag writers that should opt out of Autobase indexing when you register special-purpose feeds.
|
|
234
|
+
|
|
235
|
+
#### `await context.removeWriter(key)`
|
|
236
|
+
|
|
237
|
+
Append a dispatch that removes the provided writer key.
|
|
238
|
+
|
|
239
|
+
#### `const invites = await context.listInvites([options])`
|
|
240
|
+
|
|
241
|
+
List stored invites. Pass `{ includeRevoked: true }` to include revoked entries. Requires `user:invite`. Active records contain `id`, `roles`, `expires`, `createdBy`, `createdAt`, and an optional `revokedAt` timestamp once revoked.
|
|
242
|
+
|
|
243
|
+
#### `const revoked = await context.revokeInvite(inviteId)`
|
|
244
|
+
|
|
245
|
+
Mark the invite referenced by `inviteId` as revoked. Expects a `Buffer` containing the invite id. Requires `user:invite`. Returns `true` when an active invite was revoked.
|
|
246
|
+
|
|
247
|
+
#### `const invite = await context.createInvite(options)`
|
|
248
|
+
|
|
249
|
+
Generate a new invite string. `options` is an object such as `{ roles: ['viewer'], expires: Date.now() + 3600000 }`. The `roles` field must be an array of role names. Requires `user:invite`. Resolves to the encoded invite string while persisting metadata (`roles`, `expires`, `createdBy`, `createdAt`).
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "autobonk",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"index.js",
|
|
8
|
+
"src",
|
|
9
|
+
"README.md",
|
|
10
|
+
"CHANGELOG.md"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "npm run test:node && npm run test:bare",
|
|
14
|
+
"test:node": "brittle-node --timeout 60000 test/*.test.js",
|
|
15
|
+
"test:bare": "brittle-bare --timeout 60000 test/*.test.js",
|
|
16
|
+
"example:schema": "cd example/basic && rm -rf spec && node schema.mjs && cd ../forum && rm -rf spec && node schema.mjs",
|
|
17
|
+
"lint": "prettier --check . && lunte",
|
|
18
|
+
"format": "prettier . --write"
|
|
19
|
+
},
|
|
20
|
+
"type": "module",
|
|
21
|
+
"keywords": [],
|
|
22
|
+
"author": "Holepunch",
|
|
23
|
+
"license": "Apache-2.0",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"arg": "^5.0.2",
|
|
26
|
+
"autobase": "^7.21.1",
|
|
27
|
+
"bare-path": "^3.0.0",
|
|
28
|
+
"blind-pairing": "^2.3.1",
|
|
29
|
+
"blind-peering": "^1.15.0",
|
|
30
|
+
"corestore": "^7.5.0",
|
|
31
|
+
"hypercore-crypto": "^3.6.1",
|
|
32
|
+
"hyperdb": "^4.19.1",
|
|
33
|
+
"hyperdispatch": "^1.4.4",
|
|
34
|
+
"hyperschema": "^1.17.0",
|
|
35
|
+
"hyperswarm": "^4.14.2",
|
|
36
|
+
"protomux-wakeup": "^2.9.0",
|
|
37
|
+
"ready-resource": "^1.2.0",
|
|
38
|
+
"z32": "^1.1.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"bare-fs": "^4.5.0",
|
|
42
|
+
"bare-os": "^3.6.2",
|
|
43
|
+
"brittle": "^3.19.0",
|
|
44
|
+
"lunte": "^1.2.0",
|
|
45
|
+
"prettier": "^3.6.2",
|
|
46
|
+
"prettier-config-holepunch": "^2.0.0"
|
|
47
|
+
},
|
|
48
|
+
"imports": {
|
|
49
|
+
"os": {
|
|
50
|
+
"bare": "bare-os",
|
|
51
|
+
"default": "os"
|
|
52
|
+
},
|
|
53
|
+
"fs": {
|
|
54
|
+
"bare": "bare-fs",
|
|
55
|
+
"default": "fs"
|
|
56
|
+
},
|
|
57
|
+
"path": {
|
|
58
|
+
"bare": "bare-path",
|
|
59
|
+
"default": "path"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|