```ts
export function stitchTables(left: Array<Record<string, unknown>>, right: Array<Record<string, unknown>>, key: string, mode: string): Array<Record<string, unknown>> {
  if (mode !== 'inner' && mode !== 'left') {
    throw new Error('Invalid mode');
  }

  const rightMap = new Map<string, Record<string, unknown>>();

  for (const record of right) {
    if (typeof record[key] !== 'string') {
      throw new Error('Key value in right table must be a string');
    }
    if (rightMap.has(record[key] as string)) {
      throw new Error('Duplicate key value in right table');
    }
    rightMap.set(record[key] as string, { ...record });
  }

  const leftColumns = new Set<string>();
  for (const record of left) {
    if (typeof record[key] !== 'string') {
      throw new Error('Key value in left table must be a string');
    }
    for (const column of Object.keys(record)) {
      if (column !== key) {
        leftColumns.add(column);
      }
    }
  }

  const rightColumns = new Set<string>();
  for (const record of right) {
    for (const column of Object.keys(record)) {
      if (column !== key) {
        rightColumns.add(column);
      }
    }
  }

  for (const column of leftColumns) {
    if (rightColumns.has(column)) {
      throw new Error('Non-key column name present in both tables');
    }
  }

  const result: Array<Record<string, unknown>> = [];

  for (const leftRecord of left) {
    const rightRecord = rightMap.get(leftRecord[key] as string);
    const joinedRecord: Record<string, unknown> = { ...leftRecord };

    if (rightRecord) {
      for (const [column, value] of Object.entries(rightRecord)) {
        if (column !== key) {
          joinedRecord[column] = value;
        }
      }
    } else if (mode === 'left') {
      for (const column of rightColumns) {
        joinedRecord[column] = null;
      }
    }

    result.push(joinedRecord);
  }

  return result;
}
```