/**
* Registry Autobase apply function + encode/decode helpers.
*
* Processes: create-user, update-user, delete-user, create-notebook,
* update-notebook, delete-notebook, set-permission,
* add-writer, link-instance, unlink-instance,
* register-drive, unregister-drive,
* sync-identity
*/
export function encodeOp (op) {
return Buffer.from(JSON.stringify(op), 'utf-8')
}
export function decodeOp (buf) {
return JSON.parse(buf.toString('utf-8'))
}
export async function registryApply (batch, view, base) {
const b = view.batch()
for (const node of batch) {
try {
const op = decodeOp(node.value)
switch (op.type) {
case 'add-writer': {
const key = typeof op.writerKey === 'string'
? Buffer.from(op.writerKey, 'hex')
: op.writerKey
await base.addWriter(key, { indexer: true })
break
}
case 'create-user': {
const existing = await view.get(`user:${op.userId}`)
if (existing) break
await b.put(`user:${op.userId}`, {
userId: op.userId,
name: op.name,
role: op.role || 'read',
identityKey: op.identityKey || null,
createdAt: op.timestamp,
updatedAt: op.timestamp
})
if (op.identityKey) {
await b.put(`identity:${op.identityKey}`, { userId: op.userId })
}
break
}
case 'update-user': {
const user = await view.get(`user:${op.userId}`)
if (!user) break
const updated = { ...user.value, ...op.fields, updatedAt: op.timestamp }
await b.put(`user:${op.userId}`, updated)
break
}
case 'delete-user': {
await b.del(`user:${op.userId}`)
break
}
case 'create-notebook': {
const existing = await view.get(`notebook:${op.notebookId}`)
if (existing) break
await b.put(`notebook:${op.notebookId}`, {
notebookId: op.notebookId,
slug: op.slug,
title: op.title,
owner: op.owner,
shared: op.shared || false,
autobaseKey: op.autobaseKey || null,
createdAt: op.timestamp,
updatedAt: op.timestamp
})
await b.put(`notebook-slug:${op.slug}`, { notebookId: op.notebookId })
break
}
case 'update-notebook': {
const nb = await view.get(`notebook:${op.notebookId}`)
if (!nb) break
const updated = { ...nb.value, ...op.fields, updatedAt: op.timestamp }
await b.put(`notebook:${op.notebookId}`, updated)
break
}
case 'delete-notebook': {
const nb = await view.get(`notebook:${op.notebookId}`)
if (!nb) break
await b.del(`notebook:${op.notebookId}`)
if (nb.value.slug) {
await b.del(`notebook-slug:${nb.value.slug}`)
}
break
}
case 'set-permission': {
await b.put(`permission:${op.userId}`, {
userId: op.userId,
role: op.role,
scope: op.scope || '*',
notebookId: op.notebookId || null,
updatedAt: op.timestamp
})
break
}
case 'link-instance': {
await b.put(`instance:${op.instanceKey}`, {
instanceKey: op.instanceKey,
adminKey: op.adminKey || null,
linkedAt: op.timestamp
})
break
}
case 'unlink-instance': {
await b.del(`instance:${op.instanceKey}`)
break
}
case 'register-drive': {
await b.put(`drive:${op.slug}`, {
slug: op.slug,
driveKey: op.driveKey,
discoveryKey: op.discoveryKey,
sourceInstance: op.sourceInstance || null,
title: op.title || op.slug,
registeredAt: op.timestamp
})
break
}
case 'unregister-drive': {
await b.del(`drive:${op.slug}`)
break
}
case 'register-notebook-writer': {
await b.put(`nb-writer:${op.slug}:${op.instanceKey}`, {
slug: op.slug,
instanceKey: op.instanceKey,
writerKey: op.writerKey,
registeredAt: op.timestamp
})
break
}
case 'sync-identity': {
await b.put(`shared-user:${op.userId}`, {
userId: op.userId,
username: op.username || null,
name: op.name || '',
globalPermissions: op.globalPermissions || ['read'],
notebooks: op.notebooks || [],
publicKey: op.publicKey || null,
identityMethod: op.identityMethod || 'web-identity',
syncedAt: op.timestamp
})
if (op.publicKey) {
await b.put(`shared-pubkey:${op.publicKey}`, { userId: op.userId })
}
break
}
}
} catch (err) {
console.error('[RegistryApply] Skipping malformed op:', err.message)
continue
}
}
await b.flush()
}