toeverything/AFFiNE · error

Param not a number

Error message

Param not a number: ${mode},${numbeToken.text}

What it means

The PrimaryKeyShouldNotBeUpdated validator rejects update payloads that include the key field at all — any value, even an unchanged one. The primary key addresses rows inside the adapters' key maps, so mutating it mid-update could orphan data; updates must identify the row (where clause) and change only non-key fields.

Solutions

  1. Strip the key from the update payload: const { [keyField]: _, ...patch } = entity before validating/sending
  2. Separate insert vs update code paths so update always receives a partial patch plus the key in the where clause
  3. Add a dev-mode assertion that patch[table.keyField] === undefined

Example fix

// before
table.update({ where: { byKey: id }, data: entity }); // entity still contains id

// after
const { id: _key, ...patch } = entity;
table.update({ where: { byKey: id }, data: patch });
Defensive patterns

Strategy: validation

Validate before calling

function toPatch(keyField: string, entity: Record<string, any>) {
  const { [keyField]: _drop, ...patch } = entity;
  return patch;
}
table.update({ where: { byKey: id }, data: toPatch('id', entity) });

Type guard

null

Prevention

When it happens

Trigger: Passing a whole entity object including id to an update API instead of a partial patch; building the update payload with spread { ...entity, name: 'new' }; reusing an insert object as the update payload.

Common situations: REST-style handlers that PUT full objects; form state round-trips that carry ids; generic save() functions shared by insert and update paths.

Related errors


AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/04eadb34d3081fe8. Report an issue: GitHub.

Appendix: source

Thrown at blocksuite/affine/blocks/surface/src/utils/path-data-parser/parser.ts:103

        paramsCount = PARAMS[token.text];
        mode = token.text;
      } else {
        return parsePath('M0,0' + d);
      }
    } else if (isType(token, NUMBER)) {
      paramsCount = PARAMS[mode];
    } else {
      index++;
      paramsCount = PARAMS[token.text];
      mode = token.text;
    }
    if (index + paramsCount < tokens.length) {
      for (let i = index; i < index + paramsCount; i++) {
        const numbeToken = tokens[i];
        if (isType(numbeToken, NUMBER)) {
          params[params.length] = +numbeToken.text;
        } else {
          throw new Error(
            'Param not a number: ' + mode + ',' + numbeToken.text
          );
        }
      }
      if (typeof PARAMS[mode] === 'number') {
        const segment: Segment = { key: mode, data: params };
        segments.push(segment);
        index += paramsCount;
        token = tokens[index];
        if (mode === 'M') mode = 'L';
        if (mode === 'm') mode = 'l';
      } else {
        throw new Error('Bad segment: ' + mode);
      }
    } else {
      throw new Error('Path data ended short');
    }
  }

View on GitHub (pinned to b4c8548c09)