{"record":{"id":"7538f9f961daf412","repo":"siyuan-note/siyuan","slug":"topic-required","errorCode":null,"errorMessage":"topic required","messagePattern":"topic required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/api_event.go","lineNumber":50,"sourceCode":"\t\t}\n\t}()\n\n\tevent := rt.NewObject()\n\n\tlo.Must0(event.Set(\"handler\", goja.Null()))\n\n\tlo.Must0(event.Set(\"emit\", rt.ToValue(func(call goja.FunctionCall, rt *goja.Runtime) goja.Value {\n\t\tpromise, resolve, reject := rt.NewPromise()\n\n\t\tvar argErr error\n\t\tvar topic string\n\t\tvar eventData any\n\t\tif len(call.Arguments) < 2 {\n\t\t\targErr = fmt.Errorf(\"topic and event required\")\n\t\t} else {\n\t\t\ttopic = call.Argument(0).String()\n\t\t\tif topic == \"\" {\n\t\t\t\targErr = fmt.Errorf(\"topic required\")\n\t\t\t} else {\n\t\t\t\teventData = call.Argument(1).Export()\n\t\t\t}\n\t\t}\n\n\t\trunErr := p.worker.Run(func(rt *goja.Runtime) (result any, err error) {\n\t\t\tif argErr != nil {\n\t\t\t\terr = argErr\n\t\t\t\treturn\n\t\t\t}\n\t\t\tp.bus.Publish(topic, eventData)\n\t\t\treturn\n\t\t}, func(rt *goja.Runtime, result any, err error) {\n\t\t\tif lo.IsNil(err) {\n\t\t\t\tif resolveErr := resolve(result); resolveErr != nil {\n\t\t\t\t\tlogging.LogErrorf(\"[plugin:%s] siyuan.event.emit resolve: %v\", p.Name, resolveErr)\n\t\t\t\t}\n\t\t\t} else {","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/plugin/api_event.go#L32-L68","documentation":"Beyond the two-argument arity check, the event method validates that the topic (first argument) is a non-empty string, since an empty topic cannot be routed to any subscriber. The promise rejects with 'topic required' when the topic is ''.","triggerScenarios":"Calling emit('', data) or emit(someVariable, data) where someVariable resolves to an empty string.","commonSituations":"Topics derived from config or data keys that are missing or empty; template-built topic names where a segment was blank; state not yet initialized when the call fires.","solutions":["Supply a non-empty topic string as the first argument","Assert/log the topic value before the call to catch empty values","Skip the publish or fall back to a default topic when the value is empty","Fix the upstream source (config key, data field) that yields the empty topic"],"exampleFix":"// before\nawait siyuan.event.publish(cfg.channel || '', payload);\n// after\nif (cfg.channel) { await siyuan.event.publish(cfg.channel, payload); }","handlingStrategy":"validation","validationCode":"function publishEvent(topic, payload) {\n  if (typeof topic !== 'string' || topic.length === 0) {\n    throw new TypeError('event topic must be a non-empty string');\n  }\n  return siyuan.event.publish(topic, payload);\n}","typeGuard":"function isNonEmptyString(v) {\n  return typeof v === 'string' && v.length > 0;\n}","tryCatchPattern":"try {\n  await siyuan.event.publish(topic, payload);\n} catch (e) {\n  if (String(e.message) === 'topic required') {\n    console.warn('Skipping publish: empty topic', {payload});\n    return;\n  }\n  throw e;\n}","preventionTips":["Validate dynamic topic values before publishing","Provide fallback/default topics for config-driven channels","Never construct topics by concatenating possibly-empty segments without a check"],"tags":["plugin","event","empty-string","validation"],"backgroundTag":"empty-required-field","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}