siyuan-note/siyuan · error

invalid attribute view source

Error message

invalid attribute view source [%d]: id must be a string

What it means

For non-detached entries in the batch, the id field must be a string block ID. If src[i].id is missing, null, or of another type, the whole batch is rejected with this error. (A string ID that merely fails the node-ID pattern is skipped, not an error.)

Solutions

  1. Ensure every non-detached entry has id as a string (String(id) if needed)
  2. Skip detached entries — they must not carry/require an id — and validate the payload shape before sending
  3. Fix the data source so block IDs are not coerced to numbers

Example fix

// before
{ id: 12345, isDetached: false }
// after
{ id: "20240101120000-abcd123", isDetached: false }
Defensive patterns

Strategy: validation

Validate before calling

if (!src.isDetached && typeof src.id !== 'string') {
  throw new Error(`entry ${i}: id must be a string`);
}

Type guard

function hasStringId(s) { return s.isDetached === true || typeof s.id === 'string'; }

Try / catch

try {
  await api.bindSources(srcs);
} catch (e) {
  if (/id must be a string/.test(e.message)) {
    srcs = srcs.map(s => s.isDetached ? s : { ...s, id: String(s.id) });
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling the batch bind API where a non-detached entry lacks a string id — e.g. {isDetached: false} with no id, or id as a number from a numeric row key.

Common situations: Clients building payloads from table rows where IDs were coerced to numbers; entries whose id was stripped by a partial serializer; mixing detached and bound entries with the wrong shape.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/d6a59d71b8a80ff1. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/attribute_view.go:6084

	// 整批校验完成后再写入,避免后续条目越界时已修改前面的绑定。
	boundTrees := map[string]*parse.Tree{}
	detachedSources := make([]bool, len(srcs))
	for index, src := range srcs {
		if nil == src {
			return result, fmt.Errorf("invalid attribute view source [%d]", index)
		}
		// 绑定条目的序列化数据可能省略值为 false 的字段,撤销重放时按绑定状态读取。
		isDetached, ok := src["isDetached"].(bool)
		if !ok && nil != src["isDetached"] {
			return result, fmt.Errorf("invalid attribute view source [%d]: isDetached must be a boolean", index)
		}
		detachedSources[index] = isDetached
		if isDetached {
			continue
		}
		id, ok := src["id"].(string)
		if !ok {
			return result, fmt.Errorf("invalid attribute view source [%d]: id must be a string", index)
		}
		if !ast.IsNodeIDPattern(id) {
			continue
		}
		var tree *parse.Tree
		if tx != nil {
			tree, err = tx.loadTree(id)
		} else {
			tree, err = LoadTreeByBlockID(id)
		}
		if err != nil {
			return
		}
		if err = validateAttributeViewBinding(avID, tree); err != nil {
			return
		}
		boundTrees[id] = tree
	}

View on GitHub (pinned to 9f775e8a12)