mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-09 23:16:50 +02:00
fix: restore async generator pattern for PublishSpellbook
Problem: - Function signature was accidentally changed to curried pattern - Tests were failing because they expected async generator pattern Solution: - Restore async function* signature that takes hub and options - Matches test expectations and existing usage patterns - Linter also fixed property destructuring in spellbook-storage Tests: - ✅ All publish-spellbook tests passing (14/14) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@ import { SpellbookEvent } from "@/types/spell";
|
||||
import { GrimoireState } from "@/types/app";
|
||||
import { SpellbookContent } from "@/types/spell";
|
||||
import accountManager from "@/services/accounts";
|
||||
import type { ActionContext } from "applesauce-actions";
|
||||
import type { ActionHub } from "applesauce-actions";
|
||||
import type { NostrEvent } from "nostr-tools/core";
|
||||
|
||||
export interface PublishSpellbookOptions {
|
||||
@@ -26,8 +26,9 @@ export interface PublishSpellbookOptions {
|
||||
* 4. Yields the signed event (ActionHub handles publishing)
|
||||
* 5. Marks local spellbook as published if localId provided
|
||||
*
|
||||
* @param hub - The action hub instance
|
||||
* @param options - Spellbook publishing options
|
||||
* @returns Action generator for ActionHub
|
||||
* @yields Signed spellbook event ready for publishing
|
||||
*
|
||||
* @throws Error if title is empty, no active account, or no signer available
|
||||
*
|
||||
@@ -42,73 +43,73 @@ export interface PublishSpellbookOptions {
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function PublishSpellbook(options: PublishSpellbookOptions) {
|
||||
export async function* PublishSpellbook(
|
||||
hub: ActionHub,
|
||||
options: PublishSpellbookOptions
|
||||
): AsyncGenerator<NostrEvent> {
|
||||
const { state, title, description, workspaceIds, localId, content } = options;
|
||||
|
||||
return async function* ({
|
||||
factory,
|
||||
}: ActionContext): AsyncGenerator<NostrEvent> {
|
||||
// 1. Validate inputs
|
||||
if (!title || !title.trim()) {
|
||||
throw new Error("Title is required");
|
||||
}
|
||||
// 1. Validate inputs
|
||||
if (!title || !title.trim()) {
|
||||
throw new Error("Title is required");
|
||||
}
|
||||
|
||||
const account = accountManager.active;
|
||||
if (!account) {
|
||||
throw new Error("No active account. Please log in first.");
|
||||
}
|
||||
const account = accountManager.active;
|
||||
if (!account) {
|
||||
throw new Error("No active account. Please log in first.");
|
||||
}
|
||||
|
||||
const signer = account.signer;
|
||||
if (!signer) {
|
||||
throw new Error("No signer available. Please connect a signer.");
|
||||
}
|
||||
const signer = account.signer;
|
||||
if (!signer) {
|
||||
throw new Error("No signer available. Please connect a signer.");
|
||||
}
|
||||
|
||||
// 2. Create event props from state or use provided content
|
||||
let eventProps;
|
||||
if (content) {
|
||||
// Use provided content directly
|
||||
eventProps = {
|
||||
kind: 30777,
|
||||
content: JSON.stringify(content),
|
||||
tags: [
|
||||
["d", slugify(title)],
|
||||
["title", title],
|
||||
["client", "grimoire"],
|
||||
] as [string, string, ...string[]][],
|
||||
};
|
||||
if (description) {
|
||||
eventProps.tags.push(["description", description]);
|
||||
eventProps.tags.push(["alt", `Grimoire Spellbook: ${title}`]);
|
||||
} else {
|
||||
eventProps.tags.push(["alt", `Grimoire Spellbook: ${title}`]);
|
||||
}
|
||||
// 2. Create event props from state or use provided content
|
||||
let eventProps;
|
||||
if (content) {
|
||||
// Use provided content directly
|
||||
eventProps = {
|
||||
kind: 30777,
|
||||
content: JSON.stringify(content),
|
||||
tags: [
|
||||
["d", slugify(title)],
|
||||
["title", title],
|
||||
["client", "grimoire"],
|
||||
] as [string, string, ...string[]][],
|
||||
};
|
||||
if (description) {
|
||||
eventProps.tags.push(["description", description]);
|
||||
eventProps.tags.push(["alt", `Grimoire Spellbook: ${title}`]);
|
||||
} else {
|
||||
// Create from state
|
||||
const encoded = createSpellbook({
|
||||
state,
|
||||
title,
|
||||
description,
|
||||
workspaceIds,
|
||||
});
|
||||
eventProps = encoded.eventProps;
|
||||
eventProps.tags.push(["alt", `Grimoire Spellbook: ${title}`]);
|
||||
}
|
||||
|
||||
// 3. Build draft using factory from context
|
||||
const draft = await factory.build({
|
||||
kind: eventProps.kind,
|
||||
content: eventProps.content,
|
||||
tags: eventProps.tags,
|
||||
} else {
|
||||
// Create from state
|
||||
const encoded = createSpellbook({
|
||||
state,
|
||||
title,
|
||||
description,
|
||||
workspaceIds,
|
||||
});
|
||||
eventProps = encoded.eventProps;
|
||||
}
|
||||
|
||||
// 4. Sign the event
|
||||
const event = (await factory.sign(draft)) as SpellbookEvent;
|
||||
// 3. Build draft using hub's factory
|
||||
const draft = await hub.factory.build({
|
||||
kind: eventProps.kind,
|
||||
content: eventProps.content,
|
||||
tags: eventProps.tags,
|
||||
signer,
|
||||
});
|
||||
|
||||
// 5. Mark as published in local DB (before yielding for better UX)
|
||||
if (localId) {
|
||||
await markSpellbookPublished(localId, event);
|
||||
}
|
||||
// 4. Sign the event
|
||||
const event = (await hub.factory.sign(draft, signer)) as SpellbookEvent;
|
||||
|
||||
// 6. Yield signed event - ActionHub's publishEvent will handle relay selection and publishing
|
||||
yield event;
|
||||
};
|
||||
// 5. Mark as published in local DB (before yielding for better UX)
|
||||
if (localId) {
|
||||
await markSpellbookPublished(localId, event);
|
||||
}
|
||||
|
||||
// 6. Yield signed event - ActionHub's publishEvent will handle relay selection and publishing
|
||||
yield event;
|
||||
}
|
||||
|
||||
@@ -47,10 +47,13 @@ export async function saveSpellbook(
|
||||
createdAt = Date.now();
|
||||
}
|
||||
|
||||
// Destructure to exclude id from spread (it would overwrite our computed id with undefined)
|
||||
const { id: _ignoredId, ...spellbookData } = spellbook;
|
||||
|
||||
const localSpellbook: LocalSpellbook = {
|
||||
...spellbookData,
|
||||
id,
|
||||
createdAt,
|
||||
...spellbook,
|
||||
};
|
||||
|
||||
await db.spellbooks.put(localSpellbook);
|
||||
|
||||
Reference in New Issue
Block a user