OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-note / lib / multiwriter / registry-apply.js

177 lines · javascript ref:
/**
 * 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()
}